feat: add new onboarding home page
@@ -224,6 +224,7 @@
|
||||
"esbuild-loader": "2.18.0",
|
||||
"eslint-plugin-deprecation": "1.2.1",
|
||||
"eslint-plugin-react-hooks": "4.3.0",
|
||||
"file-loader": "6.2.0",
|
||||
"generate-json-webpack-plugin": "2.0.0",
|
||||
"glob-parent": "6.0.2",
|
||||
"html-webpack-plugin": "5.5.0",
|
||||
@@ -277,6 +278,7 @@
|
||||
"minimist": "npm:minimist-lite@2.2.0",
|
||||
"mixme": "0.5.2",
|
||||
"node-fetch": "2.6.7",
|
||||
"node-forge": "1.3.0",
|
||||
"normalize-url": "4.5.1",
|
||||
"trim-newlines": "3.0.1",
|
||||
"schema-inspector": "2.0.1",
|
||||
|
||||
BIN
public/assets/images/add-funds.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 191 KiB |
BIN
public/assets/images/no-activity.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
public/assets/images/no-funds.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
public/assets/images/onboarding/back-up-secret-key.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 256 KiB |
BIN
public/assets/images/onboarding/explore-stacks.png
Normal file
|
After Width: | Height: | Size: 327 KiB |
|
Before Width: | Height: | Size: 1.6 MiB |
BIN
public/assets/images/onboarding/help-us-improve.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 339 KiB |
BIN
public/assets/images/onboarding/set-password.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 84 KiB |
BIN
public/assets/images/onboarding/steps/add-funds-light-sm.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
public/assets/images/onboarding/steps/add-funds-light.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
public/assets/images/onboarding/steps/backup-key-light-sm.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/assets/images/onboarding/steps/backup-key-light.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
public/assets/images/onboarding/steps/buy-nft-light-sm.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
public/assets/images/onboarding/steps/buy-nft-light.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
public/assets/images/onboarding/steps/explore-apps-light-sm.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
public/assets/images/onboarding/steps/explore-apps-light.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
public/assets/images/onboarding/your-secret-key.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 132 KiB |
BIN
public/assets/images/unlock-session.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 104 KiB |
@@ -1,11 +1,14 @@
|
||||
import { Suspense } from 'react';
|
||||
import { ThemeProvider, ColorModeProvider } from '@stacks/ui';
|
||||
import { HashRouter as Router } from 'react-router-dom';
|
||||
import { QueryClientProvider } from 'react-query';
|
||||
import { Toaster } from 'react-hot-toast';
|
||||
import { Provider } from 'react-redux';
|
||||
import { PersistGate } from 'redux-persist/integration/react';
|
||||
import { ThemeProvider, ColorModeProvider } from '@stacks/ui';
|
||||
|
||||
import { theme } from '@app/common/theme';
|
||||
import { GlobalStyles } from '@app/components/global-styles/global-styles';
|
||||
import { FullPageLoadingSpinner } from '@app/components/loading-spinner';
|
||||
import { AccountsDrawer } from '@app/features/accounts-drawer/accounts-drawer';
|
||||
import { NetworksDrawer } from '@app/features/network-drawer/networks-drawer';
|
||||
import { SettingsDropdown } from '@app/features/settings-dropdown/settings-dropdown';
|
||||
@@ -13,14 +16,10 @@ import { AppErrorBoundary } from '@app/features/errors/app-error-boundary';
|
||||
import { EditNonceDrawer } from '@app/features/edit-nonce-drawer/edit-nonce-drawer';
|
||||
import { IncreaseFeeDrawer } from '@app/features/increase-fee-drawer/increase-fee-drawer';
|
||||
import { Devtools } from '@app/features/devtool/devtools';
|
||||
import { AppRoutes } from '@app/routes/app-routes';
|
||||
import { persistor, store } from '@app/store';
|
||||
import { jotaiWrappedReactQueryQueryClient as queryClient } from '@app/store/common/common.hooks';
|
||||
|
||||
import { theme } from './common/theme';
|
||||
import { FullPageLoadingSpinner } from './components/loading-spinner';
|
||||
import { GlobalStyles } from './components/global-styles/global-styles';
|
||||
import { AppRoutes } from './routes/app-routes';
|
||||
import { persistor, store } from './store';
|
||||
|
||||
const reactQueryDevToolsEnabled = false;
|
||||
|
||||
export function App() {
|
||||
|
||||
@@ -279,6 +279,3 @@ export function isEmpty(value: Object) {
|
||||
export function formatContractId(address: string, name: string) {
|
||||
return `${address}.${name}`;
|
||||
}
|
||||
|
||||
export const isFullPage = document.location.pathname.startsWith('/index.html');
|
||||
export const isPopup = !isFullPage;
|
||||
|
||||
@@ -6,8 +6,8 @@ export function CenteredPageContainer(props: FlexProps) {
|
||||
alignItems={['left', 'center']}
|
||||
flexGrow={1}
|
||||
flexDirection="column"
|
||||
height={['70vh', '90vh']}
|
||||
justifyContent={['start', 'center']}
|
||||
minHeight={['70vh', '90vh']}
|
||||
justifyContent={['start', 'center', 'center']}
|
||||
mb="loose"
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -10,16 +10,7 @@ export function ContainerLayout(props: ContainerLayoutProps) {
|
||||
return (
|
||||
<Flex flexDirection="column" flexGrow={1} width="100%" background={color('bg')}>
|
||||
{header || null}
|
||||
<Flex
|
||||
as="main"
|
||||
className="main-content"
|
||||
flexDirection="column"
|
||||
flexGrow={1}
|
||||
pb="loose"
|
||||
position="relative"
|
||||
px="loose"
|
||||
width="100%"
|
||||
>
|
||||
<Flex className="main-content" flexGrow={1} pb="loose" position="relative" width="100%">
|
||||
{children}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
export const DESKTOP_VIEWPORT_MIN_WIDTH = '480px';
|
||||
export const CENTERED_FULL_PAGE_MAX_WIDTH = '440px';
|
||||
export const HOME_FULL_PAGE_MAX_WIDTH = '832px';
|
||||
export const ONBOARDING_PAGE_MAX_WIDTH = '1208px';
|
||||
|
||||
export const fullPageStyles = css`
|
||||
.mode__full-page {
|
||||
@@ -10,8 +13,7 @@ export const fullPageStyles = css`
|
||||
max-height: unset;
|
||||
width: 100%;
|
||||
}
|
||||
main.main-content {
|
||||
flex-direction: row;
|
||||
.main-content {
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { color, Flex, FlexProps, IconButton, Stack } from '@stacks/ui';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { color, Box, Flex, FlexProps, IconButton, Stack, useMediaQuery } from '@stacks/ui';
|
||||
import { FiMoreHorizontal, FiArrowLeft } from 'react-icons/fi';
|
||||
|
||||
import { DESKTOP_VIEWPORT_MIN_WIDTH } from '@app/components/global-styles/full-page-styles';
|
||||
import { HiroWalletLogo } from '@app/components/hiro-wallet-logo';
|
||||
import { useDrawers } from '@app/common/hooks/use-drawers';
|
||||
import { isFullPage } from '@app/common/utils';
|
||||
import { NetworkModeBadge } from '@app/components/network-mode-badge';
|
||||
import { Caption, Title } from '@app/components/typography';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding.selectors';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding/onboarding.selectors';
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
import { SettingsSelectors } from '@tests/integration/settings.selectors';
|
||||
|
||||
@@ -20,8 +20,20 @@ interface HeaderProps extends FlexProps {
|
||||
export const Header: React.FC<HeaderProps> = memo(props => {
|
||||
const { onClose, title, hideActions, ...rest } = props;
|
||||
const { showSettings, setShowSettings } = useDrawers();
|
||||
const { pathname } = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [desktopViewport] = useMediaQuery(`(min-width: ${DESKTOP_VIEWPORT_MIN_WIDTH})`);
|
||||
|
||||
const hiroWalletLogoIsClickable = useMemo(() => {
|
||||
return (
|
||||
pathname !== RouteUrls.RequestDiagnostics &&
|
||||
pathname !== RouteUrls.Onboarding &&
|
||||
pathname !== RouteUrls.BackUpSecretKey &&
|
||||
pathname !== RouteUrls.SetPassword
|
||||
);
|
||||
}, [pathname]);
|
||||
|
||||
const version = useMemo(() => {
|
||||
switch (process.env.WALLET_ENVIRONMENT) {
|
||||
case 'production':
|
||||
@@ -45,19 +57,22 @@ export const Header: React.FC<HeaderProps> = memo(props => {
|
||||
{...rest}
|
||||
>
|
||||
{onClose ? (
|
||||
<IconButton alignSelf="center" icon={FiArrowLeft} iconSize="16px" onClick={onClose} />
|
||||
<Box flexBasis="20%">
|
||||
<IconButton alignSelf="center" icon={FiArrowLeft} iconSize="16px" onClick={onClose} />
|
||||
</Box>
|
||||
) : null}
|
||||
{!title && (!onClose || isFullPage) ? (
|
||||
{!title && (!onClose || desktopViewport) ? (
|
||||
<Stack
|
||||
alignItems="flex-end"
|
||||
flexGrow={1}
|
||||
flexBasis="60%"
|
||||
height="36px"
|
||||
isInline
|
||||
justifyContent={onClose ? 'center' : 'unset'}
|
||||
>
|
||||
<HiroWalletLogo
|
||||
data-testid={OnboardingSelectors.HiroWalletLogoRouteToHome}
|
||||
onClick={() => navigate(RouteUrls.Home)}
|
||||
isClickable={hiroWalletLogoIsClickable}
|
||||
onClick={hiroWalletLogoIsClickable ? () => navigate(RouteUrls.Home) : undefined}
|
||||
/>
|
||||
<Caption
|
||||
color={color('text-caption')}
|
||||
@@ -73,16 +88,18 @@ export const Header: React.FC<HeaderProps> = memo(props => {
|
||||
) : (
|
||||
<Title
|
||||
alignSelf="center"
|
||||
flexBasis="60%"
|
||||
fontSize="16px"
|
||||
fontWeight={500}
|
||||
lineHeight="24px"
|
||||
pr={hideActions ? '36px' : 'unset'}
|
||||
textAlign="center"
|
||||
{...rest}
|
||||
>
|
||||
{title}
|
||||
</Title>
|
||||
)}
|
||||
<Stack alignItems="center" isInline>
|
||||
<Stack alignItems="center" flexBasis="20%" isInline justifyContent="flex-end">
|
||||
<NetworkModeBadge />
|
||||
{!hideActions && (
|
||||
<IconButton
|
||||
|
||||
@@ -4,15 +4,20 @@ import { Stack, StackProps, color } from '@stacks/ui';
|
||||
import { HiroIcon } from './icons/hiro-icon';
|
||||
import { HiroWalletText } from './icons/hiro-wallet-text';
|
||||
|
||||
export const HiroWalletLogo = memo((props: StackProps) => {
|
||||
interface HiroWalletPageLogo extends StackProps {
|
||||
isClickable: boolean;
|
||||
}
|
||||
export const HiroWalletLogo = memo((props: HiroWalletPageLogo) => {
|
||||
const { isClickable, ...rest } = props;
|
||||
|
||||
return (
|
||||
<Stack
|
||||
_hover={{ color: color('brand') }}
|
||||
alignItems="center"
|
||||
color={color('text-title')}
|
||||
cursor="pointer"
|
||||
cursor={isClickable ? 'pointer' : 'unset'}
|
||||
isInline
|
||||
{...props}
|
||||
{...rest}
|
||||
>
|
||||
<HiroIcon width="28px" height="20px" />
|
||||
<HiroWalletText width="82px" height="14px" />
|
||||
|
||||
@@ -9,11 +9,12 @@ interface PageTitleProps extends BoxProps {
|
||||
}
|
||||
export function PageTitle(props: PageTitleProps) {
|
||||
const { children, fontSize, isHeadline, ...rest } = props;
|
||||
|
||||
return (
|
||||
<Title
|
||||
fontSize={fontSize || ['32px', '48px', isHeadline ? '64px' : '48px']}
|
||||
lineHeight={['137%', '125%', isHeadline ? '112%' : '125%']}
|
||||
maxWidth={['264px', 'unset', '500px']}
|
||||
maxWidth={['90%', '100%']}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
|
||||
18
src/app/components/secondary-button.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Button, ButtonProps, color } from '@stacks/ui';
|
||||
|
||||
export function SecondaryButton(props: ButtonProps) {
|
||||
const { children, ...rest } = props;
|
||||
return (
|
||||
<Button
|
||||
_hover={{ bg: '#EEF2FB' }}
|
||||
bg="#EEF2FB"
|
||||
borderRadius="10px"
|
||||
color={color('accent')}
|
||||
height="48px"
|
||||
type="submit"
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -11,7 +11,7 @@ const TabButton = ({
|
||||
px="base"
|
||||
py="tight"
|
||||
textAlign="center"
|
||||
borderRadius="8px"
|
||||
borderRadius="10px"
|
||||
color={isActive ? color('text-title') : color('text-caption')}
|
||||
fontSize={1}
|
||||
fontWeight={isActive ? 500 : 400}
|
||||
@@ -60,13 +60,12 @@ export function Tabs({
|
||||
}) {
|
||||
return (
|
||||
<Stack
|
||||
position="relative"
|
||||
isInline
|
||||
borderRadius="12px"
|
||||
bg={color('bg-4')}
|
||||
borderRadius="10px"
|
||||
isInline
|
||||
p="extra-tight"
|
||||
width="100%"
|
||||
flexShrink={0}
|
||||
position="relative"
|
||||
width={['100%', '193px']}
|
||||
{...rest}
|
||||
>
|
||||
<BackgroundPill alignment={activeTab === 0 ? 'start' : 'end'} />
|
||||
|
||||
@@ -1,764 +0,0 @@
|
||||
export function NoActivityIllustration() {
|
||||
return (
|
||||
<svg
|
||||
width="133"
|
||||
height="80"
|
||||
viewBox="0 0 133 80"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M128.556 80H4.44407C1.99661 80 0 77.3764 0 74.1604V47.8396C0 44.6236 1.99661 42 4.44407 42H128.556C131.003 42 133 44.6236 133 47.8396V74.1604C133 77.3764 131.003 80 128.556 80Z"
|
||||
fill="#F5F5F7"
|
||||
/>
|
||||
<path
|
||||
d="M24.3791 72.3409C30.7839 72.3409 35.976 67.1426 35.976 60.73C35.976 54.3175 30.7839 49.1191 24.3791 49.1191C17.9743 49.1191 12.7822 54.3175 12.7822 60.73C12.7822 67.1426 17.9743 72.3409 24.3791 72.3409Z"
|
||||
fill="#DCDDE2"
|
||||
/>
|
||||
<path
|
||||
d="M33.5211 72.341C36.2794 72.341 38.5154 70.1022 38.5154 67.3406C38.5154 64.579 36.2794 62.3403 33.5211 62.3403C30.7629 62.3403 28.5269 64.579 28.5269 67.3406C28.5269 70.1022 30.7629 72.341 33.5211 72.341Z"
|
||||
fill="#77797E"
|
||||
/>
|
||||
<path
|
||||
d="M29.0341 65.5609C29.0341 65.9846 28.6109 66.2389 28.6955 66.6627C28.7802 67.0864 28.6955 67.4254 28.6955 67.8492C28.6955 67.9339 28.6955 68.0187 28.6109 68.0187C28.5262 67.9339 28.5262 67.9339 28.4416 67.8492C28.2723 67.6797 28.1876 67.5949 28.1876 67.3407C28.1876 67.0017 27.9337 66.7474 27.6798 66.4932C27.5951 66.4084 27.5105 66.4084 27.4258 66.4932C27.3412 66.4932 27.1719 66.6627 27.2565 66.4932C27.3412 65.8999 26.8333 65.8999 26.4947 65.8151C26.2407 65.7304 26.2407 65.6456 26.2407 65.4761C26.2407 65.3066 26.3254 65.3066 26.4947 65.3066C26.664 65.3066 26.7486 65.2219 26.9179 65.2219C27.0872 65.1371 27.1719 65.2219 27.1719 65.3914C27.1719 65.4761 27.2565 65.6456 27.3412 65.7304L27.4258 65.8151C27.5105 65.6456 27.5951 65.4761 27.6798 65.3914C27.8491 65.2219 27.7644 65.1371 27.5951 64.9676C27.5951 64.9676 27.5951 64.7134 27.6798 64.7134C28.103 64.6286 27.9337 64.3744 27.7644 64.2049C27.5951 63.9506 27.6798 63.6964 27.9337 63.6116C28.2723 63.5269 28.1876 63.3574 28.1876 63.1879C28.1876 62.9336 28.3569 62.8489 28.5262 63.0184C28.6955 63.1031 28.7802 63.2726 29.0341 63.2726C29.1188 63.3574 29.2034 63.5269 29.3727 63.6116C29.6267 63.6964 29.6267 63.9506 29.796 64.1201C29.796 64.1201 29.796 64.2049 29.796 64.2896C29.6267 64.4591 29.542 64.7134 29.3727 64.8829C29.0341 65.3066 29.0341 65.3066 28.6109 65.1371C28.4416 65.0524 28.3569 65.0524 28.2723 65.1371C28.2723 65.2219 28.1876 65.4761 28.3569 65.5609C28.6109 65.5609 28.9495 65.5609 29.0341 65.5609ZM28.6955 63.7811C28.6955 63.7811 28.5262 63.8659 28.4416 63.8659C28.5262 63.9506 28.5262 64.1201 28.6109 64.2049C28.6955 64.3744 28.7802 64.5439 28.9495 64.7134C28.9495 64.7981 29.1188 64.7134 29.2034 64.7134C29.2881 64.7134 29.2034 64.5439 29.2881 64.5439C29.3727 64.4591 29.542 64.2049 29.542 64.2049C29.3727 64.0354 29.2881 63.7811 28.9495 63.8659C28.8648 63.7811 28.7802 63.7811 28.6955 63.7811Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M27.5948 68.8661C27.8488 68.7813 28.1027 68.8661 28.1873 69.2051C28.272 69.5441 28.5259 69.5441 28.7799 69.5441C28.8645 69.5441 28.9492 69.4593 28.9492 69.3746C28.9492 69.2898 28.8645 69.2051 28.8645 69.2051C28.8645 69.2051 28.7799 69.2051 28.6952 69.2051C28.4413 69.0356 28.4413 68.9508 28.6106 68.6118C28.6952 68.6118 28.8645 68.6118 28.8645 68.6966C28.8645 69.0356 29.1185 69.2898 29.2031 69.6288C29.2031 69.7136 29.1185 69.9678 29.1185 69.9678C28.7799 70.0526 28.4413 70.2221 28.1027 70.1373C28.0181 70.1373 27.8488 70.0526 27.7641 69.9678C27.6795 69.8831 27.5948 69.7983 27.4255 69.9678C27.2562 70.0526 27.1716 69.8831 27.0869 69.7983C27.1716 69.7136 27.2562 69.5441 27.3409 69.5441C27.6795 69.4593 27.5948 69.1203 27.5948 68.8661Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M27.0021 66.4929C27.1714 66.7472 27.2561 66.8319 27.2561 67.0014C27.2561 67.0014 27.1714 67.0862 27.0868 67.1709C27.0021 67.1709 26.9175 67.1709 26.8328 67.1709C26.5789 67.1709 26.5789 67.2557 26.5789 67.4252C26.5789 67.5099 26.5789 67.6794 26.5789 67.7642C26.5789 67.8489 26.6635 68.0184 26.6635 68.1032C26.7482 68.0184 26.9175 68.0184 27.0021 67.9337C27.2561 67.7642 27.51 67.5099 27.764 67.3404C27.8486 67.3404 27.9333 67.3404 27.9333 67.3404C28.0179 67.4252 28.0179 67.5099 28.0179 67.5947C28.0179 67.6794 27.9333 67.9337 27.8486 67.9337C27.4254 67.9337 27.2561 68.4422 26.8328 68.3574C26.5789 68.6964 26.3249 68.4422 26.071 68.2727C25.9017 68.1032 25.817 67.9337 25.9863 67.7642C26.071 67.5947 26.1556 67.5099 26.2403 67.3404C26.4096 66.7472 27.0021 67.1709 27.0021 66.4929Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M27.595 70.9848C28.1875 70.4763 28.2722 70.4763 28.3568 70.4763C28.4415 70.4763 28.5261 70.6458 28.5261 70.6458C28.4415 70.7306 28.4415 70.9001 28.2722 70.9848C28.1875 70.9848 28.1029 70.9848 28.0182 70.9001C27.9336 70.9001 27.9336 70.8153 27.8489 70.7306C27.8489 70.8153 27.595 70.8153 27.7643 70.9848C28.0182 71.3238 27.6796 71.5781 27.5104 71.8323C27.3411 72.0018 27.1718 72.0018 27.0025 71.6628C26.9178 71.4933 26.5792 71.6628 26.6639 71.4086C26.7485 71.3238 26.9178 71.2391 27.0025 71.1543C27.0871 71.0696 27.0871 71.0696 27.0871 70.9848C27.4257 70.6458 27.595 70.3916 27.595 70.9848C27.1718 71.4933 27.1718 71.4933 27.0871 71.5781L27.1718 71.6628L27.2564 71.5781C27.2564 71.4933 27.2564 71.4933 27.595 70.9848Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M33.9435 62.6792C33.8589 62.6792 33.7742 62.6792 33.6896 62.6792C33.6049 62.6792 33.6049 62.6792 33.5203 62.5945C33.5203 62.5097 33.6049 62.5097 33.6049 62.425C33.6896 62.3402 33.8589 62.2555 33.8589 62.1707C33.7742 62.0012 33.6049 62.0012 33.4356 62.086C33.1817 62.1707 33.0124 62.0012 32.9277 61.8317C32.9277 61.747 33.0124 61.6622 33.0124 61.6622C33.351 61.747 33.6049 61.4927 33.9435 61.6622C34.2821 61.8317 34.5361 62.1707 34.4514 62.5097C34.4514 62.5945 34.3668 62.5945 34.2821 62.5945C34.1975 62.6792 34.1128 62.6792 33.9435 62.6792Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.902 67.0863C25.5634 67.171 25.3941 66.7473 25.1402 67.0863C25.0555 67.0015 24.9709 66.9168 24.8862 66.832C24.8862 66.7473 24.8862 66.6625 24.9709 66.5778C25.0555 66.4083 25.2248 66.4083 25.3941 66.4083C25.6481 66.493 25.8174 66.7473 25.902 66.2388C25.9867 66.2388 26.156 66.3235 26.2406 66.3235C26.3253 66.4083 26.3253 66.493 26.2406 66.5778C26.156 66.6625 26.0713 66.7473 25.9867 66.9168C25.902 67.0863 25.902 67.0863 25.902 67.0863Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M30.3042 63.1031C30.3042 63.2726 30.3889 63.4421 30.2196 63.6116C30.1349 63.6964 30.0503 63.8659 30.0503 63.6116C29.9656 63.1031 29.5424 63.2726 29.2038 63.2726C29.2038 63.1878 29.2038 63.1878 29.1192 63.1031C28.9499 62.9336 28.9499 62.8488 29.1192 62.6793C29.2038 62.5098 29.3731 62.6793 29.3731 62.6793C29.5424 62.5946 29.7117 63.0183 29.881 62.6793C30.0503 62.9336 30.2196 63.0183 30.3042 63.1031Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M32.4202 61.069C32.2509 61.5775 31.997 62.0012 31.6584 62.3403C31.4891 62.086 31.4044 61.747 31.743 61.5775C31.8277 61.4927 31.9123 61.408 31.997 61.3232C31.9123 60.9842 32.2509 61.069 32.4202 61.069Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M24.5473 64.7981C24.5473 64.7981 24.4626 64.9676 24.378 65.1371C24.378 65.1371 24.2087 65.0524 24.124 65.1371C24.0394 65.1371 23.9547 65.3066 23.8701 65.3066C23.8701 65.3066 23.7854 65.2219 23.7008 65.2219C23.6161 65.1371 23.5315 64.9676 23.6161 64.9676C23.7854 64.7981 23.9547 64.5439 24.2087 64.3744C24.2933 64.2896 24.5473 64.4591 24.5473 64.7981Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M26.8333 64.6286C26.8333 64.7981 26.8333 64.7981 26.8333 64.8829C26.8333 64.9676 26.7486 64.9676 26.7486 65.0524C26.7486 65.0524 26.664 65.0524 26.5793 65.0524C26.5793 65.0524 26.4947 65.0524 26.4947 64.9676C26.41 64.7134 26.2407 64.5439 25.9021 64.5439C25.9021 64.5439 25.6482 64.4591 25.8175 64.2896C25.9021 64.2049 26.0714 64.1201 26.1561 64.1201C26.3254 64.2049 26.4947 64.4591 26.7486 64.4591C26.7486 64.4591 26.7486 64.6286 26.8333 64.6286Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M27.4256 63.3573C27.341 63.1878 27.2563 63.1031 27.2563 63.0183C27.341 62.8488 27.4256 62.6793 27.5949 62.5098C27.6796 62.4251 27.7642 62.3403 27.7642 62.3403C27.8489 62.4251 28.1028 62.5098 28.0182 62.5946C28.0182 62.7641 27.9335 63.0183 27.8489 63.1031C27.7642 63.1878 27.5949 63.2726 27.4256 63.3573Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M30.8966 62.1707C31.0659 62.1707 31.1505 62.4249 31.0659 62.5097C30.9812 62.5944 30.8966 62.7639 30.8966 62.9334C30.8966 63.1877 30.7273 63.1029 30.558 63.1029C30.304 63.1877 30.3887 62.9334 30.3887 62.8487C30.304 62.5097 30.558 62.3402 30.8119 62.1707C30.7273 62.1707 30.8119 62.1707 30.8966 62.1707Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M30.7269 61.1537C30.5576 61.408 30.3883 61.6622 30.219 61.8317C30.1343 61.9165 29.8804 61.747 29.8804 61.5775C29.8804 61.4927 29.965 61.408 29.965 61.3232C30.3036 61.4927 30.1343 60.8147 30.5576 60.9842C30.5576 61.069 30.6422 61.1537 30.7269 61.1537Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M28.8651 70.4765C28.8651 70.307 28.7805 70.1375 29.0344 70.1375C29.2037 70.2222 29.4576 70.307 29.6269 70.3917C29.6269 70.3917 29.1191 70.8155 29.0344 70.8155C28.7805 70.8155 28.9498 70.646 28.8651 70.4765Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M23.1933 68.7812C23.1933 68.7812 23.1933 68.866 23.1087 68.9507C23.024 68.9507 22.9394 68.866 22.8547 68.7812C22.8547 68.6965 22.9394 68.527 22.8547 68.527C22.7701 68.4422 22.6854 68.4422 22.5161 68.3575C22.4315 68.2727 22.4315 68.188 22.5161 68.1032C22.6008 68.0185 22.6854 67.9337 22.7701 68.1032C22.8547 68.2727 23.4473 68.188 23.1933 68.7812Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M26.9177 69.2051C26.833 69.2051 26.833 69.2051 26.833 69.1203C26.833 69.0356 26.833 68.9508 26.833 68.9508C27.0023 68.7813 27.1716 68.6966 27.4256 68.6118C27.4256 68.6118 27.5948 68.6118 27.5948 68.6966C27.5948 68.6966 27.5948 68.7813 27.5948 68.8661C27.5102 69.0356 27.1716 69.2051 26.9177 69.2051Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M33.097 60.8995C33.097 61.0691 33.097 61.2386 32.8431 61.2386C32.6738 61.2386 32.5045 61.1538 32.4198 60.9843C32.3352 60.8148 32.7584 60.5605 32.8431 60.5605C33.1817 60.5605 33.0124 60.8148 33.097 60.8995Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.9867 65.9845C25.7328 65.9845 25.5635 65.8997 25.7328 65.7302C25.9021 65.5607 25.5635 65.476 25.6481 65.3912C25.7328 65.3065 25.8174 65.2217 25.9021 65.137C25.9867 65.2217 26.0714 65.3065 26.156 65.3912C26.2406 65.5607 25.7328 65.815 26.156 66.0692C26.0714 65.9845 25.9867 65.9845 25.9867 65.9845Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M27.7646 64.2894C27.7646 64.5437 27.6799 64.6284 27.5106 64.5437C27.426 64.5437 27.3413 64.4589 27.3413 64.3742C27.3413 64.2894 27.3413 64.1199 27.3413 63.9504C27.3413 63.8657 27.5106 63.6962 27.5106 63.7809C27.5953 63.7809 27.6799 63.9504 27.7646 64.0352C27.7646 64.1199 27.7646 64.2047 27.7646 64.2894Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.7329 62.8487L25.8176 62.7639H25.9869C26.0715 62.9334 26.2408 63.0182 26.2408 63.1877C26.2408 63.2724 26.2408 63.4419 26.2408 63.4419C26.1562 63.4419 25.9022 63.4419 25.9022 63.3572C25.8176 63.2724 25.8176 63.1029 25.7329 62.8487Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M28.6112 71.7476C28.5265 71.6628 28.4419 71.4933 28.4419 71.4933C28.6112 71.3238 28.7805 71.239 28.9498 71.1543C29.0344 71.1543 29.1191 71.239 29.2037 71.239C29.2037 71.3238 29.1191 71.4085 29.0344 71.4933C28.9498 71.5781 28.7805 71.6628 28.6958 71.7476H28.6112Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M29.5419 62.1707C29.5419 62.2554 29.4573 62.2554 29.4573 62.3402C29.4573 62.3402 29.3726 62.3402 29.288 62.3402C29.2033 62.2554 29.034 62.1707 28.9494 62.0011C28.8647 61.9164 28.9494 61.8316 28.8647 61.7469C28.9494 61.7469 29.2033 61.6621 29.2033 61.7469C29.3726 61.8316 29.4573 62.0011 29.5419 62.1707Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M23.2779 65.6456C23.2779 65.7304 23.2779 65.8151 23.2779 65.8999C23.2779 65.9847 23.024 66.0694 22.8547 65.9846C22.77 65.8999 22.77 65.8151 22.77 65.7304C22.77 65.6456 22.8547 65.4761 22.9393 65.3914C22.9393 65.3914 23.024 65.3066 23.1086 65.3066C23.1933 65.3066 23.1933 65.3914 23.2779 65.3914C23.2779 65.5609 23.2779 65.6456 23.2779 65.6456Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M26.6641 69.0356C26.6641 69.2051 26.6641 69.2898 26.6641 69.4593C26.6641 69.4593 26.5795 69.5441 26.4948 69.5441C26.4948 69.5441 26.4102 69.5441 26.4102 69.4593V69.3746C26.6641 69.2051 26.3255 68.9508 26.4948 68.7813C26.5795 68.6966 26.5795 68.6966 26.6641 68.6118C26.6641 68.6966 26.7488 68.7813 26.7488 68.8661C26.6641 68.8661 26.6641 68.9508 26.6641 69.0356Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M27.3413 60.8994C27.5106 60.8994 27.5953 60.8994 27.5953 60.8994C27.6799 60.9842 27.7646 61.0689 27.9339 61.1537C27.8492 61.2384 27.6799 61.4079 27.6799 61.3232C27.5106 61.2384 27.426 61.0689 27.3413 60.9842C27.2567 60.9842 27.3413 60.8994 27.3413 60.8994Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M32.6734 62.8487C32.5887 62.6792 32.5041 62.5097 32.4194 62.2554C32.5041 62.2554 32.6734 62.1707 32.758 62.1707C33.012 62.1707 32.9273 62.4249 32.9273 62.5097C32.8427 62.6792 32.758 62.7639 32.6734 62.8487Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M24.8013 66.154C24.7167 65.9845 24.632 65.8998 24.5474 65.815C24.5474 65.7303 24.886 65.5608 25.0553 65.5608C25.1399 65.5608 25.1399 65.7303 25.1399 65.7303C25.0553 65.815 24.9706 65.8998 24.8013 66.154Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M24.8017 68.6965C24.5477 68.6965 24.3784 68.6965 24.3784 68.4422C24.3784 68.2727 24.4631 68.188 24.6324 68.188C24.8017 68.188 24.971 68.188 24.8863 68.4422C24.8863 68.6117 24.8017 68.6965 24.8017 68.6965Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M31.489 59.2044C31.489 59.2044 31.5737 59.2044 31.5737 59.2892C31.5737 59.4587 31.4044 59.6282 31.0658 59.6282C30.9811 59.6282 30.9811 59.5434 30.8965 59.5434C31.2351 59.4587 31.0658 59.0349 31.489 59.2044Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M22.4313 70.8155C22.3467 70.646 22.3467 70.5612 22.3467 70.4765C22.3467 70.307 22.4313 70.1375 22.6006 70.1375C22.6853 70.1375 22.8546 70.307 22.7699 70.3917C22.6853 70.4765 22.6006 70.5612 22.4313 70.8155Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M26.3251 70.1373C26.4097 70.4763 26.2404 70.4763 26.0712 70.4763C25.9865 70.4763 25.9019 70.3915 25.9019 70.3068C25.9019 70.1373 26.1558 69.9678 26.2404 69.9678C26.2404 70.0525 26.3251 70.1373 26.3251 70.1373Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.6478 67.7642C25.6478 68.0185 25.6478 68.188 25.3938 68.1032C25.3092 68.1032 25.2245 68.0185 25.1399 68.0185C25.0552 67.9337 25.3092 67.6795 25.4785 67.6795C25.5631 67.5947 25.6478 67.7642 25.6478 67.7642Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M34.5361 59.7131C34.7901 59.5436 34.8747 59.3741 35.044 59.2893L35.1287 59.3741C35.044 59.5436 34.9594 59.7978 34.9594 59.9673C34.7901 59.8826 34.7054 59.7978 34.5361 59.7131Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M34.4517 57.5095C34.5364 57.679 34.7056 57.7638 34.7903 57.9333L34.7056 58.018C34.5364 58.018 34.3671 58.018 34.1978 57.8485V57.7638C34.2824 57.679 34.3671 57.5943 34.4517 57.5095Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M35.3823 62.2554C35.0437 62.5097 34.8744 62.5944 34.7897 62.6792L34.7051 62.5097C34.7897 62.4249 34.959 62.2554 35.0437 62.1707C35.1283 62.1707 35.213 62.1707 35.3823 62.2554Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M31.235 57.1704C31.4043 57.3399 31.489 57.4247 31.5736 57.5942C31.5736 57.5942 31.489 57.6789 31.489 57.7637C31.4043 57.6789 31.235 57.5942 31.1504 57.4247C31.1504 57.3399 31.235 57.2552 31.235 57.1704Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M35.3827 60.8147C35.4674 60.9842 35.4674 61.0689 35.4674 61.1537C35.4674 61.2385 35.552 61.4927 35.2981 61.408C35.2134 61.408 35.1288 61.2385 34.9595 61.2385C35.1288 61.069 35.2134 60.9842 35.3827 60.8147Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M27.2566 63.3573C27.0873 63.4421 27.0026 63.4421 26.918 63.4421C26.8333 63.4421 26.5794 63.5269 26.664 63.2726C26.664 63.1878 26.8333 63.1031 26.8333 62.9336C26.918 63.1031 27.0873 63.1878 27.2566 63.3573Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M30.8967 60.9841C30.8967 60.8146 30.812 60.6451 30.812 60.5604C30.812 60.4756 30.9813 60.3909 30.9813 60.3909C31.066 60.4756 31.1506 60.5604 31.2353 60.6451C31.1506 60.7299 31.066 60.8146 30.8967 60.9841Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M29.204 60.9841C29.1193 60.8994 29.0347 60.8146 29.0347 60.7299C29.1193 60.6451 29.204 60.5604 29.2886 60.3909C29.3733 60.4756 29.4579 60.5604 29.4579 60.7299C29.5426 60.8146 29.3733 60.8994 29.204 60.9841Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M34.706 59.9673C34.7906 60.1368 34.7906 60.2215 34.7906 60.3063C34.7906 60.391 34.8753 60.6453 34.6213 60.5605C34.5367 60.5605 34.452 60.391 34.2827 60.391C34.452 60.2215 34.5367 60.1368 34.706 59.9673Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M35.552 60.6452C35.3827 60.4757 35.298 60.4757 35.2134 60.391L35.552 60.1367C35.552 60.1367 35.6366 60.2215 35.6366 60.3062C35.6366 60.391 35.6366 60.4757 35.552 60.6452Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M31.6584 62.5946C31.5738 62.6793 31.4891 62.7641 31.4891 62.7641C31.3198 62.7641 31.3198 62.5946 31.3198 62.5098C31.3198 62.4251 31.4045 62.3403 31.4891 62.3403C31.7431 62.2555 31.5738 62.5098 31.6584 62.5946Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M31.32 60.4756C31.2354 60.2214 31.1508 60.1366 31.1508 60.0519C31.1508 59.9671 31.0661 59.7976 31.32 59.7976C31.4047 59.7976 31.4893 59.8824 31.4893 59.9671C31.4893 60.0519 31.4047 60.2214 31.32 60.4756Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M33.0972 59.7129C32.9279 59.7129 32.7587 59.7976 32.674 59.7976L32.5894 59.6281C32.674 59.3739 32.8433 59.5434 33.0126 59.5434C33.0126 59.4586 33.0126 59.5434 33.0972 59.7129Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.648 69.6287C25.5634 69.7135 25.4787 69.7982 25.3941 69.883C25.3094 69.7982 25.2248 69.6287 25.1401 69.544C25.1401 69.544 25.2248 69.4592 25.3094 69.4592C25.4787 69.4592 25.5634 69.544 25.648 69.6287Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M28.2716 59.6283C28.1869 59.713 28.1869 59.7978 28.1869 59.7978C28.1023 59.713 27.933 59.713 27.8483 59.6283C27.679 59.5435 27.8483 59.4588 27.933 59.4588L28.1023 59.374C28.1023 59.4588 28.1869 59.5435 28.2716 59.6283Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M29.5423 61.4928C29.5423 61.4928 29.373 61.4081 29.373 61.3233C29.373 61.2386 29.373 61.1538 29.373 61.1538C29.4577 61.1538 29.5423 61.1538 29.627 61.1538C29.7116 61.2386 29.7116 61.3233 29.7963 61.4081C29.7963 61.4081 29.7116 61.4081 29.5423 61.4928Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M34.5364 59.7129C34.3671 59.7977 34.2824 59.7977 34.2824 59.7977C34.1131 59.7129 34.0285 59.6282 33.9438 59.5434C34.0285 59.4587 34.1131 59.2892 34.1131 59.3739C34.1978 59.3739 34.2824 59.4587 34.5364 59.7129Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M24.2942 70.6458C24.2942 70.6458 24.3788 70.6458 24.4635 70.6458C24.5481 70.6458 24.5481 70.8153 24.5481 70.8153L24.4635 70.9C24.3788 70.9 24.2095 70.8153 24.1249 70.7305C24.0402 70.7305 23.9556 70.6458 23.9556 70.6458C24.0402 70.6458 24.1249 70.561 24.1249 70.561C24.1249 70.6458 24.2095 70.6458 24.2942 70.6458Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M26.0714 63.8659C25.9021 63.8659 25.8174 63.9506 25.7328 63.8659C25.6481 63.8659 25.5635 63.7811 25.5635 63.6964C25.6481 63.6116 25.7328 63.5269 25.7328 63.5269C25.8174 63.6964 25.9021 63.7811 26.0714 63.8659Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.9019 71.0696C25.7326 71.1544 25.648 71.3239 25.5633 71.3239C25.5633 71.3239 25.4787 71.2391 25.394 71.1544C25.4787 71.0696 25.5633 70.9849 25.5633 70.9849C25.648 70.9849 25.7326 70.9849 25.9019 71.0696Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M20.4845 64.1201C20.3152 64.2049 20.2305 64.2049 20.0612 64.2896C20.0612 64.2049 19.9766 64.1201 19.9766 64.1201C20.0612 63.9506 20.2305 64.0353 20.3998 64.0353C20.4845 63.9506 20.4845 64.0353 20.4845 64.1201Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.1399 62.6793C25.0553 62.764 24.9706 62.8488 24.886 62.8488C24.8013 62.764 24.7167 62.6793 24.5474 62.5945C24.632 62.5945 24.7167 62.5098 24.8013 62.5098C24.9706 62.5098 24.9706 62.5945 25.1399 62.6793Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.2248 69.0355C25.2248 69.0355 25.2248 69.1203 25.1402 69.205C25.0555 69.2898 24.8862 68.9508 24.8862 68.866L24.9709 68.7813C25.0555 68.6965 25.2248 68.7813 25.2248 69.0355Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M30.6423 59.9672C30.5577 59.8824 30.473 59.7977 30.3037 59.7129C30.3037 59.7129 30.3037 59.6282 30.3884 59.6282C30.473 59.6282 30.5577 59.6282 30.727 59.6282C30.727 59.6282 30.8116 59.6282 30.8116 59.7129C30.8116 59.7977 30.8116 59.8824 30.8116 59.8824C30.8116 59.8824 30.727 59.8824 30.6423 59.9672Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M26.2405 61.3231C26.1558 61.4079 26.0712 61.4926 25.9865 61.4926C25.9865 61.4079 25.9019 61.3231 25.9019 61.3231C25.9865 61.2384 26.0712 61.1536 26.1558 60.9841C26.0712 61.1536 26.1558 61.2384 26.2405 61.3231Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M35.4675 58.9501C35.3829 58.9501 35.2982 58.9501 35.2982 58.9501C35.2136 58.8653 35.2136 58.7806 35.1289 58.6958C35.2136 58.6958 35.2136 58.6111 35.2982 58.6111C35.2982 58.6111 35.3829 58.6111 35.3829 58.6958C35.4675 58.7806 35.4675 58.7806 35.5522 58.8653C35.5522 58.9501 35.5522 58.9501 35.4675 58.9501Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M28.5263 68.6967C28.357 68.5272 28.1877 68.5272 28.103 68.4424C28.1877 68.4424 28.1877 68.3577 28.2723 68.3577C28.357 68.3577 28.5263 68.3577 28.5263 68.5272C28.5263 68.5272 28.5263 68.5272 28.5263 68.6967Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M21.1613 65.137C21.3306 65.3065 21.4153 65.3912 21.4153 65.476C21.4153 65.5607 21.246 65.6455 21.246 65.7302C21.1613 65.6455 21.0767 65.5607 21.0767 65.476C21.0767 65.3912 21.1613 65.2217 21.1613 65.137Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M35.9757 62.1706C35.8064 62.2554 35.7217 62.2554 35.5524 62.3401C35.5524 62.2554 35.4678 62.1706 35.4678 62.1706C35.5524 62.0011 35.7217 62.0859 35.891 62.0859C35.891 62.0011 35.891 62.0859 35.9757 62.1706Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M20.654 68.1881C20.5694 68.2728 20.4847 68.3576 20.4847 68.3576C20.4001 68.3576 20.3154 68.2728 20.3154 68.1881L20.4001 68.0186C20.4001 68.1033 20.4847 68.1881 20.654 68.1881Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M28.018 57.6789C27.9333 57.7637 27.8487 57.8484 27.764 57.8484C27.764 57.8484 27.6794 57.7637 27.5947 57.7637C27.6794 57.6789 27.6794 57.5942 27.764 57.5942C27.764 57.5094 27.8487 57.5942 28.018 57.6789Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.3098 57.5093C25.2251 57.5941 25.1405 57.6788 25.1405 57.6788C25.0558 57.6788 24.9712 57.5941 24.9712 57.5093L25.0558 57.3398C25.0558 57.4246 25.1405 57.4246 25.3098 57.5093Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M34.621 60.8147C34.7056 60.8994 34.7903 60.9842 34.7903 60.9842C34.7903 61.069 34.7056 61.1537 34.621 61.1537L34.4517 61.069C34.5363 60.9842 34.621 60.9842 34.621 60.8147Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M34.621 59.035C34.4517 59.1197 34.4517 59.2045 34.367 59.1197C34.2824 59.1197 34.2824 59.035 34.1978 58.9502C34.2824 58.9502 34.2824 58.8655 34.367 58.8655C34.4517 58.8655 34.4517 58.9502 34.621 59.035Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.1403 63.8659C25.0557 63.6964 25.0557 63.6964 25.0557 63.6116L25.225 63.5269C25.3096 63.6116 25.3943 63.6116 25.3943 63.6964C25.3943 63.6964 25.225 63.7811 25.1403 63.8659Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M28.1877 62.4249C28.103 62.2554 28.103 62.2554 28.103 62.1707L28.2723 62.0859C28.357 62.1707 28.4416 62.1707 28.4416 62.2554C28.4416 62.2554 28.357 62.3402 28.1877 62.4249Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.0553 72.0018C24.9706 72.0865 24.886 72.0865 24.8013 72.1713C24.7167 72.1713 24.632 72.1713 24.5474 72.0865C24.632 72.0018 24.8013 71.917 24.886 71.8323C24.9706 71.8323 24.9706 71.8323 25.0553 72.0018Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M28.7802 60.5605C28.6955 60.6453 28.6109 60.73 28.5262 60.73C28.5262 60.73 28.4416 60.6453 28.3569 60.6453C28.4416 60.5605 28.4416 60.4758 28.5262 60.4758C28.6109 60.391 28.6955 60.4758 28.7802 60.5605Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M26.2406 60.4757C26.0713 60.5604 26.0713 60.6452 25.9867 60.5604C25.902 60.5604 25.902 60.4757 25.8174 60.3909C25.902 60.3909 25.902 60.3062 25.9867 60.3062C26.0713 60.3062 26.156 60.3909 26.2406 60.4757Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M24.2938 67.5099C24.2092 67.4252 24.1245 67.3404 24.1245 67.2557C24.1245 67.2557 24.2092 67.1709 24.2092 67.0862C24.2938 67.1709 24.3785 67.1709 24.3785 67.2557C24.3785 67.3404 24.2938 67.4252 24.2938 67.5099Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M27.0871 62.0861C27.0024 61.9166 27.0024 61.9166 27.0024 61.8318L27.1717 61.7471C27.2564 61.8318 27.341 61.8318 27.341 61.9166C27.2564 61.9166 27.1717 62.0013 27.0871 62.0861Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M29.9652 71.1543C29.8806 71.239 29.7959 71.3238 29.7113 71.3238C29.7113 71.3238 29.6266 71.239 29.542 71.239C29.6266 71.1543 29.6266 71.0695 29.7113 71.0695C29.7113 70.9848 29.7959 71.0695 29.9652 71.1543Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M22.0932 71.1543C22.0085 71.239 21.9239 71.3238 21.8392 71.3238C21.8392 71.3238 21.7546 71.239 21.6699 71.239C21.7546 71.1543 21.7546 71.0695 21.8392 71.0695C21.8392 70.9848 21.9239 71.0695 22.0932 71.1543Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M23.7859 60.391C23.7859 60.4758 23.7012 60.5605 23.7012 60.5605C23.6166 60.4758 23.5319 60.391 23.4473 60.2215C23.4473 60.2215 23.4473 60.1368 23.5319 60.052C23.6166 60.2215 23.7012 60.3063 23.7859 60.391Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M35.7217 62.8486C35.7217 62.9334 35.8063 62.9334 35.8063 63.0181C35.8063 63.1029 35.7217 63.1876 35.7217 63.2724C35.637 63.1876 35.5524 63.1029 35.5524 63.0181C35.4677 63.0181 35.637 62.9334 35.7217 62.8486Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M32.2507 59.374C32.3353 59.4588 32.42 59.5435 32.42 59.6283C32.42 59.713 32.3353 59.713 32.2507 59.7978C32.2507 59.713 32.166 59.713 32.166 59.6283C32.166 59.5435 32.166 59.4588 32.2507 59.374Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M24.8014 70.3915C24.7168 70.222 24.7168 70.222 24.7168 70.1372L24.8861 70.0525C24.9707 70.1372 25.0554 70.1372 25.0554 70.222C25.0554 70.222 24.9707 70.3067 24.8014 70.3915Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M21.8394 63.1877C21.9241 63.2725 22.0087 63.3572 22.0087 63.442C22.0087 63.5267 21.9241 63.5267 21.8394 63.6115C21.8394 63.5267 21.7548 63.5267 21.7548 63.442C21.6701 63.3572 21.7548 63.3572 21.8394 63.1877Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M23.8701 68.6119C23.9548 68.5271 23.9548 68.5271 24.0394 68.4424C24.0394 68.4424 24.1241 68.4424 24.2087 68.5271C24.1241 68.6119 24.0394 68.6966 24.0394 68.7814C23.9548 68.6966 23.9548 68.6966 23.8701 68.6119Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M24.0394 72.3409C23.9548 72.2562 23.8701 72.2562 23.8701 72.2562C23.9548 72.1714 24.0394 72.0867 24.1241 72.0867C24.1241 72.1714 24.2087 72.1714 24.2087 72.2562C24.2087 72.1714 24.1241 72.2562 24.0394 72.3409Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M26.9177 66.1541C27.0023 66.2388 27.087 66.3236 27.1716 66.4083C27.087 66.4083 27.087 66.4931 27.0023 66.4931C26.9177 66.4083 26.9177 66.4083 26.833 66.3236C26.833 66.2388 26.833 66.2388 26.9177 66.1541Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M23.1086 62.086C23.1086 62.1707 23.024 62.2555 23.024 62.2555C22.9393 62.1707 22.8547 62.086 22.77 62.086L22.8547 62.0012C22.9393 62.0012 23.024 62.086 23.1086 62.086Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M33.3516 62.5098C33.3516 62.5946 33.267 62.5946 33.267 62.5946C33.1823 62.5946 33.0977 62.5946 33.0977 62.5098C33.0977 62.5098 33.0977 62.425 33.1823 62.425C33.267 62.5098 33.267 62.5098 33.3516 62.5098Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M24.2935 71.6629C24.2935 71.6629 24.2088 71.6629 24.1242 71.7477C24.1242 71.6629 24.0396 71.6629 24.0396 71.5782C24.0396 71.4934 24.1242 71.4934 24.1242 71.4087C24.2088 71.4934 24.2088 71.5782 24.2935 71.6629Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M30.7267 63.5267C30.7267 63.5267 30.7267 63.442 30.6421 63.3572C30.7267 63.3572 30.7267 63.2725 30.8114 63.2725C30.896 63.2725 30.896 63.3572 30.9807 63.3572C30.896 63.442 30.896 63.442 30.7267 63.5267Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M30.3885 60.052C30.4732 60.1368 30.5578 60.1368 30.5578 60.1368C30.4732 60.1368 30.4732 60.2215 30.3885 60.2215C30.3885 60.2215 30.3039 60.2215 30.2192 60.1368C30.3885 60.1368 30.3885 60.052 30.3885 60.052Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M29.1193 65.3914C29.204 65.3914 29.204 65.4761 29.1193 65.3914C29.1193 65.4761 29.1193 65.4761 29.1193 65.5609L29.0347 65.4761C29.1193 65.4761 29.1193 65.3914 29.1193 65.3914Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M97.4532 66.8261H52.4619C51.8532 66.8261 51.2998 66.0634 51.2998 65.0464C51.2998 64.1141 51.7979 63.2666 52.4619 63.2666H97.4532C98.0619 63.2666 98.6153 64.0294 98.6153 65.0464C98.6153 66.0634 98.0619 66.8261 97.4532 66.8261Z"
|
||||
fill="#DCDDE2"
|
||||
/>
|
||||
<path
|
||||
d="M112.782 58.8596H52.4619C51.8532 58.8596 51.2998 58.0968 51.2998 57.0798C51.2998 56.1476 51.7979 55.3 52.4619 55.3H112.838C113.446 55.3 114 56.0628 114 57.0798C113.944 58.0968 113.446 58.8596 112.782 58.8596Z"
|
||||
fill="#DCDDE2"
|
||||
/>
|
||||
<path
|
||||
d="M128.556 38H4.44407C1.99661 38 0 35.3764 0 32.1604V5.83964C0 2.62361 1.99661 0 4.44407 0H128.556C131.003 0 133 2.62361 133 5.83964V32.1604C133 35.3764 131.003 38 128.556 38Z"
|
||||
fill="#F5F5F7"
|
||||
/>
|
||||
<path
|
||||
d="M24.3791 30.3409C30.7839 30.3409 35.976 25.1426 35.976 18.73C35.976 12.3175 30.7839 7.11914 24.3791 7.11914C17.9743 7.11914 12.7822 12.3175 12.7822 18.73C12.7822 25.1426 17.9743 30.3409 24.3791 30.3409Z"
|
||||
fill="#DCDDE2"
|
||||
/>
|
||||
<path
|
||||
d="M33.5211 30.341C36.2794 30.341 38.5154 28.1022 38.5154 25.3406C38.5154 22.579 36.2794 20.3403 33.5211 20.3403C30.7629 20.3403 28.5269 22.579 28.5269 25.3406C28.5269 28.1022 30.7629 30.341 33.5211 30.341Z"
|
||||
fill="#77797E"
|
||||
/>
|
||||
<path
|
||||
d="M29.0341 23.5609C29.0341 23.9846 28.6109 24.2389 28.6955 24.6627C28.7802 25.0864 28.6955 25.4254 28.6955 25.8492C28.6955 25.9339 28.6955 26.0187 28.6109 26.0187C28.5262 25.9339 28.5262 25.9339 28.4416 25.8492C28.2723 25.6797 28.1876 25.5949 28.1876 25.3407C28.1876 25.0017 27.9337 24.7474 27.6798 24.4932C27.5951 24.4084 27.5105 24.4084 27.4258 24.4932C27.3412 24.4932 27.1719 24.6627 27.2565 24.4932C27.3412 23.8999 26.8333 23.8999 26.4947 23.8151C26.2407 23.7304 26.2407 23.6456 26.2407 23.4761C26.2407 23.3066 26.3254 23.3066 26.4947 23.3066C26.664 23.3066 26.7486 23.2219 26.9179 23.2219C27.0872 23.1371 27.1719 23.2219 27.1719 23.3914C27.1719 23.4761 27.2565 23.6456 27.3412 23.7304L27.4258 23.8151C27.5105 23.6456 27.5951 23.4761 27.6798 23.3914C27.8491 23.2219 27.7644 23.1371 27.5951 22.9676C27.5951 22.9676 27.5951 22.7134 27.6798 22.7134C28.103 22.6286 27.9337 22.3744 27.7644 22.2049C27.5951 21.9506 27.6798 21.6964 27.9337 21.6116C28.2723 21.5269 28.1876 21.3574 28.1876 21.1879C28.1876 20.9336 28.3569 20.8489 28.5262 21.0184C28.6955 21.1031 28.7802 21.2726 29.0341 21.2726C29.1188 21.3574 29.2034 21.5269 29.3727 21.6116C29.6267 21.6964 29.6267 21.9506 29.796 22.1201C29.796 22.1201 29.796 22.2049 29.796 22.2896C29.6267 22.4591 29.542 22.7134 29.3727 22.8829C29.0341 23.3066 29.0341 23.3066 28.6109 23.1371C28.4416 23.0524 28.3569 23.0524 28.2723 23.1371C28.2723 23.2219 28.1876 23.4761 28.3569 23.5609C28.6109 23.5609 28.9495 23.5609 29.0341 23.5609ZM28.6955 21.7811C28.6955 21.7811 28.5262 21.8659 28.4416 21.8659C28.5262 21.9506 28.5262 22.1201 28.6109 22.2049C28.6955 22.3744 28.7802 22.5439 28.9495 22.7134C28.9495 22.7981 29.1188 22.7134 29.2034 22.7134C29.2881 22.7134 29.2034 22.5439 29.2881 22.5439C29.3727 22.4591 29.542 22.2049 29.542 22.2049C29.3727 22.0354 29.2881 21.7811 28.9495 21.8659C28.8648 21.7811 28.7802 21.7811 28.6955 21.7811Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M27.5948 26.8661C27.8488 26.7813 28.1027 26.8661 28.1873 27.2051C28.272 27.5441 28.5259 27.5441 28.7799 27.5441C28.8645 27.5441 28.9492 27.4593 28.9492 27.3746C28.9492 27.2898 28.8645 27.2051 28.8645 27.2051C28.8645 27.2051 28.7799 27.2051 28.6952 27.2051C28.4413 27.0356 28.4413 26.9508 28.6106 26.6118C28.6952 26.6118 28.8645 26.6118 28.8645 26.6966C28.8645 27.0356 29.1185 27.2898 29.2031 27.6288C29.2031 27.7136 29.1185 27.9678 29.1185 27.9678C28.7799 28.0526 28.4413 28.2221 28.1027 28.1373C28.0181 28.1373 27.8488 28.0526 27.7641 27.9678C27.6795 27.8831 27.5948 27.7983 27.4255 27.9678C27.2562 28.0526 27.1716 27.8831 27.0869 27.7983C27.1716 27.7136 27.2562 27.5441 27.3409 27.5441C27.6795 27.4593 27.5948 27.1203 27.5948 26.8661Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M27.0021 24.4929C27.1714 24.7472 27.2561 24.8319 27.2561 25.0014C27.2561 25.0014 27.1714 25.0862 27.0868 25.1709C27.0021 25.1709 26.9175 25.1709 26.8328 25.1709C26.5789 25.1709 26.5789 25.2557 26.5789 25.4252C26.5789 25.5099 26.5789 25.6794 26.5789 25.7642C26.5789 25.8489 26.6635 26.0184 26.6635 26.1032C26.7482 26.0184 26.9175 26.0184 27.0021 25.9337C27.2561 25.7642 27.51 25.5099 27.764 25.3404C27.8486 25.3404 27.9333 25.3404 27.9333 25.3404C28.0179 25.4252 28.0179 25.5099 28.0179 25.5947C28.0179 25.6794 27.9333 25.9337 27.8486 25.9337C27.4254 25.9337 27.2561 26.4422 26.8328 26.3574C26.5789 26.6964 26.3249 26.4422 26.071 26.2727C25.9017 26.1032 25.817 25.9337 25.9863 25.7642C26.071 25.5947 26.1556 25.5099 26.2403 25.3404C26.4096 24.7472 27.0021 25.1709 27.0021 24.4929Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M27.595 28.9848C28.1875 28.4763 28.2722 28.4763 28.3568 28.4763C28.4415 28.4763 28.5261 28.6458 28.5261 28.6458C28.4415 28.7306 28.4415 28.9001 28.2722 28.9848C28.1875 28.9848 28.1029 28.9848 28.0182 28.9001C27.9336 28.9001 27.9336 28.8153 27.8489 28.7306C27.8489 28.8153 27.595 28.8153 27.7643 28.9848C28.0182 29.3238 27.6796 29.5781 27.5104 29.8323C27.3411 30.0018 27.1718 30.0018 27.0025 29.6628C26.9178 29.4933 26.5792 29.6628 26.6639 29.4086C26.7485 29.3238 26.9178 29.2391 27.0025 29.1543C27.0871 29.0696 27.0871 29.0696 27.0871 28.9848C27.4257 28.6458 27.595 28.3916 27.595 28.9848C27.1718 29.4933 27.1718 29.4933 27.0871 29.5781L27.1718 29.6628L27.2564 29.5781C27.2564 29.4933 27.2564 29.4933 27.595 28.9848Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M33.9435 20.6792C33.8589 20.6792 33.7742 20.6792 33.6896 20.6792C33.6049 20.6792 33.6049 20.6792 33.5203 20.5945C33.5203 20.5097 33.6049 20.5097 33.6049 20.425C33.6896 20.3402 33.8589 20.2555 33.8589 20.1707C33.7742 20.0012 33.6049 20.0012 33.4356 20.086C33.1817 20.1707 33.0124 20.0012 32.9277 19.8317C32.9277 19.747 33.0124 19.6622 33.0124 19.6622C33.351 19.747 33.6049 19.4927 33.9435 19.6622C34.2821 19.8317 34.5361 20.1707 34.4514 20.5097C34.4514 20.5945 34.3668 20.5945 34.2821 20.5945C34.1975 20.6792 34.1128 20.6792 33.9435 20.6792Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.902 25.0863C25.5634 25.171 25.3941 24.7473 25.1402 25.0863C25.0555 25.0015 24.9709 24.9168 24.8862 24.832C24.8862 24.7473 24.8862 24.6625 24.9709 24.5778C25.0555 24.4083 25.2248 24.4083 25.3941 24.4083C25.6481 24.493 25.8174 24.7473 25.902 24.2388C25.9867 24.2388 26.156 24.3235 26.2406 24.3235C26.3253 24.4083 26.3253 24.493 26.2406 24.5778C26.156 24.6625 26.0713 24.7473 25.9867 24.9168C25.902 25.0863 25.902 25.0863 25.902 25.0863Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M30.3042 21.1031C30.3042 21.2726 30.3889 21.4421 30.2196 21.6116C30.1349 21.6964 30.0503 21.8659 30.0503 21.6116C29.9656 21.1031 29.5424 21.2726 29.2038 21.2726C29.2038 21.1878 29.2038 21.1878 29.1192 21.1031C28.9499 20.9336 28.9499 20.8488 29.1192 20.6793C29.2038 20.5098 29.3731 20.6793 29.3731 20.6793C29.5424 20.5946 29.7117 21.0183 29.881 20.6793C30.0503 20.9336 30.2196 21.0183 30.3042 21.1031Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M32.4202 19.069C32.2509 19.5775 31.997 20.0012 31.6584 20.3403C31.4891 20.086 31.4044 19.747 31.743 19.5775C31.8277 19.4927 31.9123 19.408 31.997 19.3232C31.9123 18.9842 32.2509 19.069 32.4202 19.069Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M24.5473 22.7981C24.5473 22.7981 24.4626 22.9676 24.378 23.1371C24.378 23.1371 24.2087 23.0524 24.124 23.1371C24.0394 23.1371 23.9547 23.3066 23.8701 23.3066C23.8701 23.3066 23.7854 23.2219 23.7008 23.2219C23.6161 23.1371 23.5315 22.9676 23.6161 22.9676C23.7854 22.7981 23.9547 22.5439 24.2087 22.3744C24.2933 22.2896 24.5473 22.4591 24.5473 22.7981Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M26.8333 22.6286C26.8333 22.7981 26.8333 22.7981 26.8333 22.8829C26.8333 22.9676 26.7486 22.9676 26.7486 23.0524C26.7486 23.0524 26.664 23.0524 26.5793 23.0524C26.5793 23.0524 26.4947 23.0524 26.4947 22.9676C26.41 22.7134 26.2407 22.5439 25.9021 22.5439C25.9021 22.5439 25.6482 22.4591 25.8175 22.2896C25.9021 22.2049 26.0714 22.1201 26.1561 22.1201C26.3254 22.2049 26.4947 22.4591 26.7486 22.4591C26.7486 22.4591 26.7486 22.6286 26.8333 22.6286Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M27.4256 21.3573C27.341 21.1878 27.2563 21.1031 27.2563 21.0183C27.341 20.8488 27.4256 20.6793 27.5949 20.5098C27.6796 20.4251 27.7642 20.3403 27.7642 20.3403C27.8489 20.4251 28.1028 20.5098 28.0182 20.5946C28.0182 20.7641 27.9335 21.0183 27.8489 21.1031C27.7642 21.1878 27.5949 21.2726 27.4256 21.3573Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M30.8966 20.1707C31.0659 20.1707 31.1505 20.4249 31.0659 20.5097C30.9812 20.5944 30.8966 20.7639 30.8966 20.9334C30.8966 21.1877 30.7273 21.1029 30.558 21.1029C30.304 21.1877 30.3887 20.9334 30.3887 20.8487C30.304 20.5097 30.558 20.3402 30.8119 20.1707C30.7273 20.1707 30.8119 20.1707 30.8966 20.1707Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M30.7269 19.1537C30.5576 19.408 30.3883 19.6622 30.219 19.8317C30.1343 19.9165 29.8804 19.747 29.8804 19.5775C29.8804 19.4927 29.965 19.408 29.965 19.3232C30.3036 19.4927 30.1343 18.8147 30.5576 18.9842C30.5576 19.069 30.6422 19.1537 30.7269 19.1537Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M28.8651 28.4765C28.8651 28.307 28.7805 28.1375 29.0344 28.1375C29.2037 28.2222 29.4576 28.307 29.6269 28.3917C29.6269 28.3917 29.1191 28.8155 29.0344 28.8155C28.7805 28.8155 28.9498 28.646 28.8651 28.4765Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M23.1933 26.7812C23.1933 26.7812 23.1933 26.866 23.1087 26.9507C23.024 26.9507 22.9394 26.866 22.8547 26.7812C22.8547 26.6965 22.9394 26.527 22.8547 26.527C22.7701 26.4422 22.6854 26.4422 22.5161 26.3575C22.4315 26.2727 22.4315 26.188 22.5161 26.1032C22.6008 26.0185 22.6854 25.9337 22.7701 26.1032C22.8547 26.2727 23.4473 26.188 23.1933 26.7812Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M26.9177 27.2051C26.833 27.2051 26.833 27.2051 26.833 27.1203C26.833 27.0356 26.833 26.9508 26.833 26.9508C27.0023 26.7813 27.1716 26.6966 27.4256 26.6118C27.4256 26.6118 27.5948 26.6118 27.5948 26.6966C27.5948 26.6966 27.5948 26.7813 27.5948 26.8661C27.5102 27.0356 27.1716 27.2051 26.9177 27.2051Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M33.097 18.8995C33.097 19.0691 33.097 19.2386 32.8431 19.2386C32.6738 19.2386 32.5045 19.1538 32.4198 18.9843C32.3352 18.8148 32.7584 18.5605 32.8431 18.5605C33.1817 18.5605 33.0124 18.8148 33.097 18.8995Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.9867 23.9845C25.7328 23.9845 25.5635 23.8997 25.7328 23.7302C25.9021 23.5607 25.5635 23.476 25.6481 23.3912C25.7328 23.3065 25.8174 23.2217 25.9021 23.137C25.9867 23.2217 26.0714 23.3065 26.156 23.3912C26.2406 23.5607 25.7328 23.815 26.156 24.0692C26.0714 23.9845 25.9867 23.9845 25.9867 23.9845Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M27.7646 22.2894C27.7646 22.5437 27.6799 22.6284 27.5106 22.5437C27.426 22.5437 27.3413 22.4589 27.3413 22.3742C27.3413 22.2894 27.3413 22.1199 27.3413 21.9504C27.3413 21.8657 27.5106 21.6962 27.5106 21.7809C27.5953 21.7809 27.6799 21.9504 27.7646 22.0352C27.7646 22.1199 27.7646 22.2047 27.7646 22.2894Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.7329 20.8487L25.8176 20.7639H25.9869C26.0715 20.9334 26.2408 21.0182 26.2408 21.1877C26.2408 21.2724 26.2408 21.4419 26.2408 21.4419C26.1562 21.4419 25.9022 21.4419 25.9022 21.3572C25.8176 21.2724 25.8176 21.1029 25.7329 20.8487Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M28.6112 29.7476C28.5265 29.6628 28.4419 29.4933 28.4419 29.4933C28.6112 29.3238 28.7805 29.239 28.9498 29.1543C29.0344 29.1543 29.1191 29.239 29.2037 29.239C29.2037 29.3238 29.1191 29.4085 29.0344 29.4933C28.9498 29.5781 28.7805 29.6628 28.6958 29.7476H28.6112Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M29.5419 20.1707C29.5419 20.2554 29.4573 20.2554 29.4573 20.3402C29.4573 20.3402 29.3726 20.3402 29.288 20.3402C29.2033 20.2554 29.034 20.1707 28.9494 20.0011C28.8647 19.9164 28.9494 19.8316 28.8647 19.7469C28.9494 19.7469 29.2033 19.6621 29.2033 19.7469C29.3726 19.8316 29.4573 20.0011 29.5419 20.1707Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M23.2779 23.6456C23.2779 23.7304 23.2779 23.8151 23.2779 23.8999C23.2779 23.9847 23.024 24.0694 22.8547 23.9846C22.77 23.8999 22.77 23.8151 22.77 23.7304C22.77 23.6456 22.8547 23.4761 22.9393 23.3914C22.9393 23.3914 23.024 23.3066 23.1086 23.3066C23.1933 23.3066 23.1933 23.3914 23.2779 23.3914C23.2779 23.5609 23.2779 23.6456 23.2779 23.6456Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M26.6641 27.0356C26.6641 27.2051 26.6641 27.2898 26.6641 27.4593C26.6641 27.4593 26.5795 27.5441 26.4948 27.5441C26.4948 27.5441 26.4102 27.5441 26.4102 27.4593V27.3746C26.6641 27.2051 26.3255 26.9508 26.4948 26.7813C26.5795 26.6966 26.5795 26.6966 26.6641 26.6118C26.6641 26.6966 26.7488 26.7813 26.7488 26.8661C26.6641 26.8661 26.6641 26.9508 26.6641 27.0356Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M27.3413 18.8994C27.5106 18.8994 27.5953 18.8994 27.5953 18.8994C27.6799 18.9842 27.7646 19.0689 27.9339 19.1537C27.8492 19.2384 27.6799 19.4079 27.6799 19.3232C27.5106 19.2384 27.426 19.0689 27.3413 18.9842C27.2567 18.9842 27.3413 18.8994 27.3413 18.8994Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M32.6734 20.8487C32.5887 20.6792 32.5041 20.5097 32.4194 20.2554C32.5041 20.2554 32.6734 20.1707 32.758 20.1707C33.012 20.1707 32.9273 20.4249 32.9273 20.5097C32.8427 20.6792 32.758 20.7639 32.6734 20.8487Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M24.8013 24.154C24.7167 23.9845 24.632 23.8998 24.5474 23.815C24.5474 23.7303 24.886 23.5608 25.0553 23.5608C25.1399 23.5608 25.1399 23.7303 25.1399 23.7303C25.0553 23.815 24.9706 23.8998 24.8013 24.154Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M24.8017 26.6965C24.5477 26.6965 24.3784 26.6965 24.3784 26.4422C24.3784 26.2727 24.4631 26.188 24.6324 26.188C24.8017 26.188 24.971 26.188 24.8863 26.4422C24.8863 26.6117 24.8017 26.6965 24.8017 26.6965Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M31.489 17.2044C31.489 17.2044 31.5737 17.2044 31.5737 17.2892C31.5737 17.4587 31.4044 17.6282 31.0658 17.6282C30.9811 17.6282 30.9811 17.5434 30.8965 17.5434C31.2351 17.4587 31.0658 17.0349 31.489 17.2044Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M22.4313 28.8155C22.3467 28.646 22.3467 28.5612 22.3467 28.4765C22.3467 28.307 22.4313 28.1375 22.6006 28.1375C22.6853 28.1375 22.8546 28.307 22.7699 28.3917C22.6853 28.4765 22.6006 28.5612 22.4313 28.8155Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M26.3251 28.1373C26.4097 28.4763 26.2404 28.4763 26.0712 28.4763C25.9865 28.4763 25.9019 28.3915 25.9019 28.3068C25.9019 28.1373 26.1558 27.9678 26.2404 27.9678C26.2404 28.0525 26.3251 28.1373 26.3251 28.1373Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.6478 25.7642C25.6478 26.0185 25.6478 26.188 25.3938 26.1032C25.3092 26.1032 25.2245 26.0185 25.1399 26.0185C25.0552 25.9337 25.3092 25.6795 25.4785 25.6795C25.5631 25.5947 25.6478 25.7642 25.6478 25.7642Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M34.5361 17.7131C34.7901 17.5436 34.8747 17.3741 35.044 17.2893L35.1287 17.3741C35.044 17.5436 34.9594 17.7978 34.9594 17.9673C34.7901 17.8826 34.7054 17.7978 34.5361 17.7131Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M34.4517 15.5095C34.5364 15.679 34.7056 15.7638 34.7903 15.9333L34.7056 16.018C34.5364 16.018 34.3671 16.018 34.1978 15.8485V15.7638C34.2824 15.679 34.3671 15.5943 34.4517 15.5095Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M35.3823 20.2554C35.0437 20.5097 34.8744 20.5944 34.7897 20.6792L34.7051 20.5097C34.7897 20.4249 34.959 20.2554 35.0437 20.1707C35.1283 20.1707 35.213 20.1707 35.3823 20.2554Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M31.235 15.1704C31.4043 15.3399 31.489 15.4247 31.5736 15.5942C31.5736 15.5942 31.489 15.6789 31.489 15.7637C31.4043 15.6789 31.235 15.5942 31.1504 15.4247C31.1504 15.3399 31.235 15.2552 31.235 15.1704Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M35.3827 18.8147C35.4674 18.9842 35.4674 19.0689 35.4674 19.1537C35.4674 19.2385 35.552 19.4927 35.2981 19.408C35.2134 19.408 35.1288 19.2385 34.9595 19.2385C35.1288 19.069 35.2134 18.9842 35.3827 18.8147Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M27.2566 21.3573C27.0873 21.4421 27.0026 21.4421 26.918 21.4421C26.8333 21.4421 26.5794 21.5269 26.664 21.2726C26.664 21.1878 26.8333 21.1031 26.8333 20.9336C26.918 21.1031 27.0873 21.1878 27.2566 21.3573Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M30.8967 18.9841C30.8967 18.8146 30.812 18.6451 30.812 18.5604C30.812 18.4756 30.9813 18.3909 30.9813 18.3909C31.066 18.4756 31.1506 18.5604 31.2353 18.6451C31.1506 18.7299 31.066 18.8146 30.8967 18.9841Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M29.204 18.9841C29.1193 18.8994 29.0347 18.8146 29.0347 18.7299C29.1193 18.6451 29.204 18.5604 29.2886 18.3909C29.3733 18.4756 29.4579 18.5604 29.4579 18.7299C29.5426 18.8146 29.3733 18.8994 29.204 18.9841Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M34.706 17.9673C34.7906 18.1368 34.7906 18.2215 34.7906 18.3063C34.7906 18.391 34.8753 18.6453 34.6213 18.5605C34.5367 18.5605 34.452 18.391 34.2827 18.391C34.452 18.2215 34.5367 18.1368 34.706 17.9673Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M35.552 18.6452C35.3827 18.4757 35.298 18.4757 35.2134 18.391L35.552 18.1367C35.552 18.1367 35.6366 18.2215 35.6366 18.3062C35.6366 18.391 35.6366 18.4757 35.552 18.6452Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M31.6584 20.5946C31.5738 20.6793 31.4891 20.7641 31.4891 20.7641C31.3198 20.7641 31.3198 20.5946 31.3198 20.5098C31.3198 20.4251 31.4045 20.3403 31.4891 20.3403C31.7431 20.2555 31.5738 20.5098 31.6584 20.5946Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M31.32 18.4756C31.2354 18.2214 31.1508 18.1366 31.1508 18.0519C31.1508 17.9671 31.0661 17.7976 31.32 17.7976C31.4047 17.7976 31.4893 17.8824 31.4893 17.9671C31.4893 18.0519 31.4047 18.2214 31.32 18.4756Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M33.0972 17.7129C32.9279 17.7129 32.7587 17.7976 32.674 17.7976L32.5894 17.6281C32.674 17.3739 32.8433 17.5434 33.0126 17.5434C33.0126 17.4586 33.0126 17.5434 33.0972 17.7129Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.648 27.6287C25.5634 27.7135 25.4787 27.7982 25.3941 27.883C25.3094 27.7982 25.2248 27.6287 25.1401 27.544C25.1401 27.544 25.2248 27.4592 25.3094 27.4592C25.4787 27.4592 25.5634 27.544 25.648 27.6287Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M28.2716 17.6283C28.1869 17.713 28.1869 17.7978 28.1869 17.7978C28.1023 17.713 27.933 17.713 27.8483 17.6283C27.679 17.5435 27.8483 17.4588 27.933 17.4588L28.1023 17.374C28.1023 17.4588 28.1869 17.5435 28.2716 17.6283Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M29.5423 19.4928C29.5423 19.4928 29.373 19.4081 29.373 19.3233C29.373 19.2386 29.373 19.1538 29.373 19.1538C29.4577 19.1538 29.5423 19.1538 29.627 19.1538C29.7116 19.2386 29.7116 19.3233 29.7963 19.4081C29.7963 19.4081 29.7116 19.4081 29.5423 19.4928Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M34.5364 17.7129C34.3671 17.7977 34.2824 17.7977 34.2824 17.7977C34.1131 17.7129 34.0285 17.6282 33.9438 17.5434C34.0285 17.4587 34.1131 17.2892 34.1131 17.3739C34.1978 17.3739 34.2824 17.4587 34.5364 17.7129Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M24.2942 28.6458C24.2942 28.6458 24.3788 28.6458 24.4635 28.6458C24.5481 28.6458 24.5481 28.8153 24.5481 28.8153L24.4635 28.9C24.3788 28.9 24.2095 28.8153 24.1249 28.7305C24.0402 28.7305 23.9556 28.6458 23.9556 28.6458C24.0402 28.6458 24.1249 28.561 24.1249 28.561C24.1249 28.6458 24.2095 28.6458 24.2942 28.6458Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M26.0714 21.8659C25.9021 21.8659 25.8174 21.9506 25.7328 21.8659C25.6481 21.8659 25.5635 21.7811 25.5635 21.6964C25.6481 21.6116 25.7328 21.5269 25.7328 21.5269C25.8174 21.6964 25.9021 21.7811 26.0714 21.8659Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.9019 29.0696C25.7326 29.1544 25.648 29.3239 25.5633 29.3239C25.5633 29.3239 25.4787 29.2391 25.394 29.1544C25.4787 29.0696 25.5633 28.9849 25.5633 28.9849C25.648 28.9849 25.7326 28.9849 25.9019 29.0696Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M20.4845 22.1201C20.3152 22.2049 20.2305 22.2049 20.0612 22.2896C20.0612 22.2049 19.9766 22.1201 19.9766 22.1201C20.0612 21.9506 20.2305 22.0353 20.3998 22.0353C20.4845 21.9506 20.4845 22.0353 20.4845 22.1201Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.1399 20.6793C25.0553 20.764 24.9706 20.8488 24.886 20.8488C24.8013 20.764 24.7167 20.6793 24.5474 20.5945C24.632 20.5945 24.7167 20.5098 24.8013 20.5098C24.9706 20.5098 24.9706 20.5945 25.1399 20.6793Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.2248 27.0355C25.2248 27.0355 25.2248 27.1203 25.1402 27.205C25.0555 27.2898 24.8862 26.9508 24.8862 26.866L24.9709 26.7813C25.0555 26.6965 25.2248 26.7813 25.2248 27.0355Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M30.6423 17.9672C30.5577 17.8824 30.473 17.7977 30.3037 17.7129C30.3037 17.7129 30.3037 17.6282 30.3884 17.6282C30.473 17.6282 30.5577 17.6282 30.727 17.6282C30.727 17.6282 30.8116 17.6282 30.8116 17.7129C30.8116 17.7977 30.8116 17.8824 30.8116 17.8824C30.8116 17.8824 30.727 17.8824 30.6423 17.9672Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M26.2405 19.3231C26.1558 19.4079 26.0712 19.4926 25.9865 19.4926C25.9865 19.4079 25.9019 19.3231 25.9019 19.3231C25.9865 19.2384 26.0712 19.1536 26.1558 18.9841C26.0712 19.1536 26.1558 19.2384 26.2405 19.3231Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M35.4675 16.9501C35.3829 16.9501 35.2982 16.9501 35.2982 16.9501C35.2136 16.8653 35.2136 16.7806 35.1289 16.6958C35.2136 16.6958 35.2136 16.6111 35.2982 16.6111C35.2982 16.6111 35.3829 16.6111 35.3829 16.6958C35.4675 16.7806 35.4675 16.7806 35.5522 16.8653C35.5522 16.9501 35.5522 16.9501 35.4675 16.9501Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M28.5263 26.6967C28.357 26.5272 28.1877 26.5272 28.103 26.4424C28.1877 26.4424 28.1877 26.3577 28.2723 26.3577C28.357 26.3577 28.5263 26.3577 28.5263 26.5272C28.5263 26.5272 28.5263 26.5272 28.5263 26.6967Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M21.1613 23.137C21.3306 23.3065 21.4153 23.3912 21.4153 23.476C21.4153 23.5607 21.246 23.6455 21.246 23.7302C21.1613 23.6455 21.0767 23.5607 21.0767 23.476C21.0767 23.3912 21.1613 23.2217 21.1613 23.137Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M35.9757 20.1706C35.8064 20.2554 35.7217 20.2554 35.5524 20.3401C35.5524 20.2554 35.4678 20.1706 35.4678 20.1706C35.5524 20.0011 35.7217 20.0859 35.891 20.0859C35.891 20.0011 35.891 20.0859 35.9757 20.1706Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M20.654 26.1881C20.5694 26.2728 20.4847 26.3576 20.4847 26.3576C20.4001 26.3576 20.3154 26.2728 20.3154 26.1881L20.4001 26.0186C20.4001 26.1033 20.4847 26.1881 20.654 26.1881Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M28.018 15.6789C27.9333 15.7637 27.8487 15.8484 27.764 15.8484C27.764 15.8484 27.6794 15.7637 27.5947 15.7637C27.6794 15.6789 27.6794 15.5942 27.764 15.5942C27.764 15.5094 27.8487 15.5942 28.018 15.6789Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.3098 15.5093C25.2251 15.5941 25.1405 15.6788 25.1405 15.6788C25.0558 15.6788 24.9712 15.5941 24.9712 15.5093L25.0558 15.3398C25.0558 15.4246 25.1405 15.4246 25.3098 15.5093Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M34.621 18.8147C34.7056 18.8994 34.7903 18.9842 34.7903 18.9842C34.7903 19.069 34.7056 19.1537 34.621 19.1537L34.4517 19.069C34.5363 18.9842 34.621 18.9842 34.621 18.8147Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M34.621 17.035C34.4517 17.1197 34.4517 17.2045 34.367 17.1197C34.2824 17.1197 34.2824 17.035 34.1978 16.9502C34.2824 16.9502 34.2824 16.8655 34.367 16.8655C34.4517 16.8655 34.4517 16.9502 34.621 17.035Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.1403 21.8659C25.0557 21.6964 25.0557 21.6964 25.0557 21.6116L25.225 21.5269C25.3096 21.6116 25.3943 21.6116 25.3943 21.6964C25.3943 21.6964 25.225 21.7811 25.1403 21.8659Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M28.1877 20.4249C28.103 20.2554 28.103 20.2554 28.103 20.1707L28.2723 20.0859C28.357 20.1707 28.4416 20.1707 28.4416 20.2554C28.4416 20.2554 28.357 20.3402 28.1877 20.4249Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M25.0553 30.0018C24.9706 30.0865 24.886 30.0865 24.8013 30.1713C24.7167 30.1713 24.632 30.1713 24.5474 30.0865C24.632 30.0018 24.8013 29.917 24.886 29.8323C24.9706 29.8323 24.9706 29.8323 25.0553 30.0018Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M28.7802 18.5605C28.6955 18.6453 28.6109 18.73 28.5262 18.73C28.5262 18.73 28.4416 18.6453 28.3569 18.6453C28.4416 18.5605 28.4416 18.4758 28.5262 18.4758C28.6109 18.391 28.6955 18.4758 28.7802 18.5605Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M26.2406 18.4757C26.0713 18.5604 26.0713 18.6452 25.9867 18.5604C25.902 18.5604 25.902 18.4757 25.8174 18.3909C25.902 18.3909 25.902 18.3062 25.9867 18.3062C26.0713 18.3062 26.156 18.3909 26.2406 18.4757Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M24.2938 25.5099C24.2092 25.4252 24.1245 25.3404 24.1245 25.2557C24.1245 25.2557 24.2092 25.1709 24.2092 25.0862C24.2938 25.1709 24.3785 25.1709 24.3785 25.2557C24.3785 25.3404 24.2938 25.4252 24.2938 25.5099Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M27.0871 20.0861C27.0024 19.9166 27.0024 19.9166 27.0024 19.8318L27.1717 19.7471C27.2564 19.8318 27.341 19.8318 27.341 19.9166C27.2564 19.9166 27.1717 20.0013 27.0871 20.0861Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M29.9652 29.1543C29.8806 29.239 29.7959 29.3238 29.7113 29.3238C29.7113 29.3238 29.6266 29.239 29.542 29.239C29.6266 29.1543 29.6266 29.0695 29.7113 29.0695C29.7113 28.9848 29.7959 29.0695 29.9652 29.1543Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M22.0932 29.1543C22.0085 29.239 21.9239 29.3238 21.8392 29.3238C21.8392 29.3238 21.7546 29.239 21.6699 29.239C21.7546 29.1543 21.7546 29.0695 21.8392 29.0695C21.8392 28.9848 21.9239 29.0695 22.0932 29.1543Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M23.7859 18.391C23.7859 18.4758 23.7012 18.5605 23.7012 18.5605C23.6166 18.4758 23.5319 18.391 23.4473 18.2215C23.4473 18.2215 23.4473 18.1368 23.5319 18.052C23.6166 18.2215 23.7012 18.3063 23.7859 18.391Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M35.7217 20.8486C35.7217 20.9334 35.8063 20.9334 35.8063 21.0181C35.8063 21.1029 35.7217 21.1876 35.7217 21.2724C35.637 21.1876 35.5524 21.1029 35.5524 21.0181C35.4677 21.0181 35.637 20.9334 35.7217 20.8486Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M32.2507 17.374C32.3353 17.4588 32.42 17.5435 32.42 17.6283C32.42 17.713 32.3353 17.713 32.2507 17.7978C32.2507 17.713 32.166 17.713 32.166 17.6283C32.166 17.5435 32.166 17.4588 32.2507 17.374Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M24.8014 28.3915C24.7168 28.222 24.7168 28.222 24.7168 28.1372L24.8861 28.0525C24.9707 28.1372 25.0554 28.1372 25.0554 28.222C25.0554 28.222 24.9707 28.3067 24.8014 28.3915Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M21.8394 21.1877C21.9241 21.2725 22.0087 21.3572 22.0087 21.442C22.0087 21.5267 21.9241 21.5267 21.8394 21.6115C21.8394 21.5267 21.7548 21.5267 21.7548 21.442C21.6701 21.3572 21.7548 21.3572 21.8394 21.1877Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M23.8701 26.6119C23.9548 26.5271 23.9548 26.5271 24.0394 26.4424C24.0394 26.4424 24.1241 26.4424 24.2087 26.5271C24.1241 26.6119 24.0394 26.6966 24.0394 26.7814C23.9548 26.6966 23.9548 26.6966 23.8701 26.6119Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M24.0394 30.3409C23.9548 30.2562 23.8701 30.2562 23.8701 30.2562C23.9548 30.1714 24.0394 30.0867 24.1241 30.0867C24.1241 30.1714 24.2087 30.1714 24.2087 30.2562C24.2087 30.1714 24.1241 30.2562 24.0394 30.3409Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M26.9177 24.1541C27.0023 24.2388 27.087 24.3236 27.1716 24.4083C27.087 24.4083 27.087 24.4931 27.0023 24.4931C26.9177 24.4083 26.9177 24.4083 26.833 24.3236C26.833 24.2388 26.833 24.2388 26.9177 24.1541Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M23.1086 20.086C23.1086 20.1707 23.024 20.2555 23.024 20.2555C22.9393 20.1707 22.8547 20.086 22.77 20.086L22.8547 20.0012C22.9393 20.0012 23.024 20.086 23.1086 20.086Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M33.3516 20.5098C33.3516 20.5946 33.267 20.5946 33.267 20.5946C33.1823 20.5946 33.0977 20.5946 33.0977 20.5098C33.0977 20.5098 33.0977 20.425 33.1823 20.425C33.267 20.5098 33.267 20.5098 33.3516 20.5098Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M24.2935 29.6629C24.2935 29.6629 24.2088 29.6629 24.1242 29.7477C24.1242 29.6629 24.0396 29.6629 24.0396 29.5782C24.0396 29.4934 24.1242 29.4934 24.1242 29.4087C24.2088 29.4934 24.2088 29.5782 24.2935 29.6629Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M30.7267 21.5267C30.7267 21.5267 30.7267 21.442 30.6421 21.3572C30.7267 21.3572 30.7267 21.2725 30.8114 21.2725C30.896 21.2725 30.896 21.3572 30.9807 21.3572C30.896 21.442 30.896 21.442 30.7267 21.5267Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M30.3885 18.052C30.4732 18.1368 30.5578 18.1368 30.5578 18.1368C30.4732 18.1368 30.4732 18.2215 30.3885 18.2215C30.3885 18.2215 30.3039 18.2215 30.2192 18.1368C30.3885 18.1368 30.3885 18.052 30.3885 18.052Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M29.1193 23.3914C29.204 23.3914 29.204 23.4761 29.1193 23.3914C29.1193 23.4761 29.1193 23.4761 29.1193 23.5609L29.0347 23.4761C29.1193 23.4761 29.1193 23.3914 29.1193 23.3914Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
<path
|
||||
d="M97.4532 24.8261H52.4619C51.8532 24.8261 51.2998 24.0634 51.2998 23.0464C51.2998 22.1141 51.7979 21.2666 52.4619 21.2666H97.4532C98.0619 21.2666 98.6153 22.0294 98.6153 23.0464C98.6153 24.0634 98.0619 24.8261 97.4532 24.8261Z"
|
||||
fill="#DCDDE2"
|
||||
/>
|
||||
<path
|
||||
d="M112.782 16.8596H52.4619C51.8532 16.8596 51.2998 16.0968 51.2998 15.0798C51.2998 14.1476 51.7979 13.3 52.4619 13.3H112.838C113.446 13.3 114 14.0628 114 15.0798C113.944 16.0968 113.446 16.8596 112.782 16.8596Z"
|
||||
fill="#DCDDE2"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
import { Box, Stack } from '@stacks/ui';
|
||||
import { Stack } from '@stacks/ui';
|
||||
|
||||
import { Caption } from '@app/components/typography';
|
||||
import { NoActivityIllustration } from '@app/components/vector/no-activity';
|
||||
import NoActivity from '@assets/images/no-activity.png';
|
||||
|
||||
export function NoAccountActivity() {
|
||||
return (
|
||||
<Stack py="extra-loose" spacing="extra-loose" justifyContent="center" alignItems="center">
|
||||
<Box mx="auto">
|
||||
<NoActivityIllustration />
|
||||
</Box>
|
||||
|
||||
<img src={NoActivity} width="120px" />
|
||||
<Caption maxWidth="23ch" textAlign="center">
|
||||
No activity yet.
|
||||
</Caption>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Stack, StackProps } from '@stacks/ui';
|
||||
|
||||
import { AssetRow } from '@app/components/asset-row';
|
||||
import { CollectibleAssets } from '@app/features/balances-list/components/collectible-assets';
|
||||
import { useCurrentAccount } from '@app/store/accounts/account.hooks';
|
||||
|
||||
import { useCurrentAccountUnanchoredBalances } from '@app/query/balance/balance.hooks';
|
||||
import { useStxTokenState } from '@app/store/assets/asset.hooks';
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
|
||||
import { FungibleAssets } from './components/fungible-assets';
|
||||
import { NoAssets } from './components/no-assets';
|
||||
|
||||
@@ -14,8 +16,10 @@ interface BalancesListProps extends StackProps {
|
||||
}
|
||||
export const BalancesList = ({ address, ...props }: BalancesListProps) => {
|
||||
const stxToken = useStxTokenState(address);
|
||||
const currentAccount = useCurrentAccount();
|
||||
const { data: balances } = useCurrentAccountUnanchoredBalances();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleFundAccount = useCallback(() => navigate(RouteUrls.Buy), [navigate]);
|
||||
|
||||
if (!balances) return null;
|
||||
|
||||
@@ -24,8 +28,7 @@ export const BalancesList = ({ address, ...props }: BalancesListProps) => {
|
||||
Object.keys(balances.fungible_tokens).length === 0 &&
|
||||
Object.keys(balances.non_fungible_tokens).length === 0;
|
||||
|
||||
if (noAssets && currentAccount?.address)
|
||||
return <NoAssets address={currentAccount.address} {...props} />;
|
||||
if (noAssets) return <NoAssets onFundAccount={handleFundAccount} {...props} />;
|
||||
|
||||
return (
|
||||
<Stack pb="extra-loose" spacing="extra-loose" {...props}>
|
||||
|
||||
@@ -1,35 +1,34 @@
|
||||
import { Caption } from '@app/components/typography';
|
||||
import { NoAssetsEmptyIllustration } from '@app/components/vector/no-assets';
|
||||
import { Button, color, Stack, StackProps, useClipboard } from '@stacks/ui';
|
||||
import { UserAreaSelectors } from '@tests/integration/user-area.selectors';
|
||||
import { Flex, FlexProps } from '@stacks/ui';
|
||||
|
||||
interface NoAssetProps extends StackProps {
|
||||
address: string;
|
||||
import { Caption } from '@app/components/typography';
|
||||
import NoFunds from '@assets/images/no-funds.png';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding/onboarding.selectors';
|
||||
import { SecondaryButton } from '@app/components/secondary-button';
|
||||
|
||||
interface NoAssetProps extends FlexProps {
|
||||
onFundAccount(): void;
|
||||
}
|
||||
export function NoAssets({ address, ...props }: NoAssetProps) {
|
||||
const { onCopy, hasCopied } = useClipboard(address || '');
|
||||
export function NoAssets({ onFundAccount, ...props }: NoAssetProps) {
|
||||
return (
|
||||
<Stack
|
||||
py="extra-loose"
|
||||
spacing="extra-loose"
|
||||
justifyContent="center"
|
||||
<Flex
|
||||
alignItems="center"
|
||||
flexDirection="column"
|
||||
justifyContent="center"
|
||||
py="extra-loose"
|
||||
{...props}
|
||||
>
|
||||
<NoAssetsEmptyIllustration maxWidth="120px" />
|
||||
<Caption maxWidth="23ch" textAlign="center">
|
||||
Get started by sending some STX to your wallet.
|
||||
<img src={NoFunds} width="88px" />
|
||||
<Caption maxWidth="248px" mt="extra-loose" textAlign="center">
|
||||
This is where you’ll see your balances. Get some STX to get started.
|
||||
</Caption>
|
||||
<Button
|
||||
bg="#EEF2FB"
|
||||
_hover={{ bg: '#E5EBFA' }}
|
||||
color={color('brand')}
|
||||
borderRadius="10px"
|
||||
onClick={onCopy}
|
||||
data-testid={UserAreaSelectors.AccountBalancesCopyAddress}
|
||||
<SecondaryButton
|
||||
data-testid={OnboardingSelectors.NoAssetsFundAccountLink}
|
||||
height="36px"
|
||||
mt="base"
|
||||
onClick={onFundAccount}
|
||||
>
|
||||
{hasCopied ? 'Copied!' : 'Copy address'}
|
||||
</Button>
|
||||
</Stack>
|
||||
Fund your account
|
||||
</SecondaryButton>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Box, color } from '@stacks/ui';
|
||||
|
||||
import { Text } from '@app/components/typography';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding.selectors';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding/onboarding.selectors';
|
||||
|
||||
interface SecretKeyWordProps {
|
||||
word: string;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Box, color, Stack } from '@stacks/ui';
|
||||
|
||||
import { Text, Title } from '@app/components/typography';
|
||||
import { Link } from '@app/components/link';
|
||||
import YourSecretKey from '@assets/images/onboarding/your-secret-key.svg';
|
||||
import YourSecretKey from '@assets/images/onboarding/your-secret-key.png';
|
||||
|
||||
import { SecretKeyWord } from './components/secret-key-word';
|
||||
import { SettingsSelectors } from '@tests/integration/settings.selectors';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import ReactDOM from 'react-dom';
|
||||
import { App } from './app';
|
||||
|
||||
import { persistAndRenderApp } from '@app/common/persistence';
|
||||
import { initSentry } from '@shared/utils/sentry-init';
|
||||
@@ -8,6 +7,8 @@ import { store } from './store';
|
||||
import { InternalMethods } from '@shared/message-types';
|
||||
import { inMemoryKeyActions } from './store/in-memory-key/in-memory-key.actions';
|
||||
|
||||
import { App } from './app';
|
||||
|
||||
initSentry();
|
||||
void initSegment();
|
||||
|
||||
|
||||
@@ -18,6 +18,13 @@ import {
|
||||
useUpdateNetworkState,
|
||||
} from '@app/store/network/networks.hooks';
|
||||
|
||||
interface AddNetworkFormValues {
|
||||
key: string;
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
const addNetworkFormValues: AddNetworkFormValues = { key: '', name: '', url: '' };
|
||||
|
||||
export const AddNetwork = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
@@ -30,7 +37,7 @@ export const AddNetwork = () => {
|
||||
return (
|
||||
<CenteredPageContainer>
|
||||
<Formik
|
||||
initialValues={{ name: '', url: '', key: '' }}
|
||||
initialValues={addNetworkFormValues}
|
||||
onSubmit={async values => {
|
||||
const { name, url, key } = values;
|
||||
if (!isValidUrl(url)) {
|
||||
@@ -70,7 +77,7 @@ export const AddNetwork = () => {
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Stack
|
||||
maxWidth={CENTERED_FULL_PAGE_MAX_WIDTH}
|
||||
px={['unset', 'base-loose']}
|
||||
px={['loose', 'base-loose']}
|
||||
spacing="loose"
|
||||
textAlign={['left', 'center']}
|
||||
>
|
||||
|
||||
@@ -6,8 +6,8 @@ import { CenteredPageContainer } from '@app/components/centered-page-container';
|
||||
import { PrimaryButton } from '@app/components/primary-button';
|
||||
import { Text, Title } from '@app/components/typography';
|
||||
import { CENTERED_FULL_PAGE_MAX_WIDTH } from '@app/components/global-styles/full-page-styles';
|
||||
import HelpUsImprove from '@assets/images/onboarding/help-us-improve.svg';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding.selectors';
|
||||
import HelpUsImprove from '@assets/images/onboarding/help-us-improve.png';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding/onboarding.selectors';
|
||||
|
||||
interface ReasonToAllowDiagnosticsProps {
|
||||
text: string;
|
||||
@@ -35,6 +35,7 @@ export function AllowDiagnosticsLayout(props: AllowDiagnosticsLayoutProps) {
|
||||
<Stack
|
||||
maxWidth={CENTERED_FULL_PAGE_MAX_WIDTH}
|
||||
mt={['loose', 'unset']}
|
||||
px={['loose', 'unset']}
|
||||
spacing="extra-loose"
|
||||
textAlign="left"
|
||||
>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { openInNewTab } from '@app/common/utils/open-in-new-tab';
|
||||
import { Text } from '@app/components/typography';
|
||||
import { Link } from '@app/components/link';
|
||||
import { PageTitle } from '@app/components/page-title';
|
||||
import AddFunds from '@assets/images/add-funds.svg';
|
||||
import AddFunds from '@assets/images/add-funds.png';
|
||||
import { CenteredPageContainer } from '@app/components/centered-page-container';
|
||||
import { CENTERED_FULL_PAGE_MAX_WIDTH } from '@app/components/global-styles/full-page-styles';
|
||||
|
||||
@@ -21,13 +21,16 @@ export const BuyLayout = (props: BuyLayoutProps) => {
|
||||
|
||||
return (
|
||||
<CenteredPageContainer>
|
||||
<Stack maxWidth={CENTERED_FULL_PAGE_MAX_WIDTH} pb={['loose', 'unset']} spacing="base">
|
||||
<Stack
|
||||
maxWidth={CENTERED_FULL_PAGE_MAX_WIDTH}
|
||||
pb={['loose', 'unset']}
|
||||
px={['loose', 'unset']}
|
||||
spacing="base"
|
||||
>
|
||||
<Box width={['100px', '115px']}>
|
||||
<img src={AddFunds} />
|
||||
</Box>
|
||||
<PageTitle fontSize={[4, 8]} mt={['unset', 'base']}>
|
||||
Fund your account
|
||||
</PageTitle>
|
||||
<PageTitle mt={['unset', 'base']}>Fund your account</PageTitle>
|
||||
<Text>
|
||||
Fund your account with STX, the native currency of Stacks. You can use your STX to trade,
|
||||
bid in auctions, earn Bitcoin, and much more. Buy some STX on an exchange to get started.
|
||||
|
||||
@@ -25,7 +25,7 @@ export const ChooseAccount = memo(() => {
|
||||
}, [handleUnmount]);
|
||||
|
||||
return (
|
||||
<Flex flexDirection="column">
|
||||
<Flex flexDirection="column" px={['loose', 'unset']} width="100%">
|
||||
<Stack spacing="loose" textAlign="center">
|
||||
<AppIcon mt="extra-loose" mb="loose" size="72px" />
|
||||
<Stack spacing="base">
|
||||
|
||||
@@ -1,10 +1,25 @@
|
||||
import { memo, Suspense } from 'react';
|
||||
import { FiPlus } from 'react-icons/fi';
|
||||
import { ChainID } from '@stacks/transactions';
|
||||
import { ButtonProps } from '@stacks/ui';
|
||||
|
||||
import { useHasFiatProviders } from '@app/query/hiro-config/hiro-config.query';
|
||||
import { BuyTxButton } from './tx-button';
|
||||
import { useCurrentNetworkState } from '@app/store/network/networks.hooks';
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
import { BuyTokensSelectors } from '@tests/page-objects/buy-tokens-selectors';
|
||||
|
||||
import { TxButton } from './tx-button';
|
||||
|
||||
const BuyTxButton = (props: ButtonProps) => (
|
||||
<TxButton
|
||||
data-testid={BuyTokensSelectors.BtnBuyTokens}
|
||||
icon={FiPlus}
|
||||
route={RouteUrls.Buy}
|
||||
type="Buy"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
const BuyButtonFallback = memo(() => <BuyTxButton isDisabled />);
|
||||
|
||||
export const BuyButton = () => {
|
||||
@@ -15,7 +30,7 @@ export const BuyButton = () => {
|
||||
|
||||
return (
|
||||
<Suspense fallback={<BuyButtonFallback />}>
|
||||
<BuyTxButton data-testid={BuyTokensSelectors.BtnBuyTokens} />
|
||||
<BuyTxButton />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,15 +2,15 @@ import { Suspense } from 'react';
|
||||
import { Stack, StackProps } from '@stacks/ui';
|
||||
|
||||
import { BuyButton } from './buy-button';
|
||||
import { ReceiveButton } from './receive-button';
|
||||
import { SendButton } from './send-button';
|
||||
import { ReceiveTxButton } from './tx-button';
|
||||
|
||||
export const HomeActions = (props: StackProps) => {
|
||||
return (
|
||||
<Suspense fallback={<></>}>
|
||||
<Stack isInline spacing="base-tight" {...props}>
|
||||
<Stack isInline mt={['base', 'base', 'unset']} spacing="base-tight" {...props}>
|
||||
<SendButton />
|
||||
<ReceiveTxButton />
|
||||
<ReceiveButton />
|
||||
<BuyButton />
|
||||
</Stack>
|
||||
</Suspense>
|
||||
|
||||
@@ -26,7 +26,7 @@ export function HomeTabs(props: HomeTabsProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack flexGrow={1} spacing="extra-loose" {...rest}>
|
||||
<Stack flexGrow={1} mt="loose" spacing="extra-loose" {...rest}>
|
||||
<Tabs
|
||||
tabs={[
|
||||
{ slug: 'balances', label: 'Balances' },
|
||||
|
||||
141
src/app/pages/home/components/onboarding-step-item.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import { FiArrowRight, FiCheck } from 'react-icons/fi';
|
||||
import { Box, Circle, color, Flex, Stack, useMediaQuery } from '@stacks/ui';
|
||||
|
||||
import { DESKTOP_VIEWPORT_MIN_WIDTH } from '@app/components/global-styles/full-page-styles';
|
||||
import { Tooltip } from '@app/components/tooltip';
|
||||
import { Body, Caption, Text, Title } from '@app/components/typography';
|
||||
import { Link } from '@app/components/link';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding/onboarding.selectors';
|
||||
import { RouteType } from '@shared/models/onboarding-types';
|
||||
|
||||
const externalLinkInfo =
|
||||
'This link will take you to an external third-party website that is not affiliated with Hiro Systems PBC.';
|
||||
|
||||
interface StepIllustrationProps {
|
||||
image: string;
|
||||
}
|
||||
const StepIllustration = ({ image }: StepIllustrationProps) => <img src={image} />;
|
||||
|
||||
const PopupDoneIcon = () => (
|
||||
<Circle border="1px solid" borderColor={color('border')} color={color('bg')} size="40px">
|
||||
<FiCheck color={color('accent')} />
|
||||
</Circle>
|
||||
);
|
||||
|
||||
interface OnboardingStepItemProps {
|
||||
action: string;
|
||||
body: string;
|
||||
imageFull: string;
|
||||
imagePopup: string;
|
||||
isDone: boolean;
|
||||
onClick(): void;
|
||||
routeType: number;
|
||||
title: string;
|
||||
}
|
||||
export function OnboardingStepItem(props: OnboardingStepItemProps) {
|
||||
const { action, body, imageFull, imagePopup, isDone, onClick, routeType, title } = props;
|
||||
|
||||
const [desktopViewport] = useMediaQuery(`(min-width: ${DESKTOP_VIEWPORT_MIN_WIDTH})`);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
_hover={{ cursor: !desktopViewport && !isDone ? 'pointer' : 'unset' }}
|
||||
border={['1px solid', 'unset']}
|
||||
borderColor={color('border')}
|
||||
borderRadius={['10px', 'unset']}
|
||||
flexGrow={1}
|
||||
onClick={!desktopViewport && !isDone ? onClick : undefined}
|
||||
pl={['base', 'unset']}
|
||||
pr="base"
|
||||
py={['base', 'unset']}
|
||||
spacing="base"
|
||||
>
|
||||
<Box height={['46px', '100px']} width={['55px', '132px']}>
|
||||
{desktopViewport ? (
|
||||
<StepIllustration image={imageFull} />
|
||||
) : !isDone ? (
|
||||
<StepIllustration image={imagePopup} />
|
||||
) : (
|
||||
<PopupDoneIcon />
|
||||
)}
|
||||
</Box>
|
||||
<Tooltip
|
||||
disabled={desktopViewport || routeType !== RouteType.External}
|
||||
label={externalLinkInfo}
|
||||
maxWidth="250px"
|
||||
placement="top"
|
||||
>
|
||||
<Flex alignItems={['center', 'unset']} flexDirection={['unset', 'column']}>
|
||||
<Title
|
||||
color={!desktopViewport && isDone ? color('text-caption') : color('text-title')}
|
||||
fontSize={[1, 2]}
|
||||
lineHeight="24px"
|
||||
mr="extra-tight"
|
||||
>
|
||||
{title}
|
||||
</Title>
|
||||
{!desktopViewport && routeType === RouteType.External ? (
|
||||
<Box as={FiArrowRight} transform={'rotate(-45deg)'} />
|
||||
) : null}
|
||||
|
||||
<Body display={['none', 'block']} mt="tight">
|
||||
{body}
|
||||
</Body>
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
{desktopViewport ? (
|
||||
isDone ? (
|
||||
<Box
|
||||
data-testid={OnboardingSelectors.StepItemDone}
|
||||
border="2px solid"
|
||||
borderColor={color('bg-4')}
|
||||
borderRadius="25px"
|
||||
color={color('text-caption')}
|
||||
height="24px"
|
||||
width="62px"
|
||||
>
|
||||
<Stack
|
||||
alignItems="center"
|
||||
height="100%"
|
||||
isInline
|
||||
justifyContent="center"
|
||||
spacing="extra-tight"
|
||||
>
|
||||
<FiCheck />
|
||||
<Caption fontWeight={400} variant="c2">
|
||||
Done
|
||||
</Caption>
|
||||
</Stack>
|
||||
</Box>
|
||||
) : (
|
||||
<Link
|
||||
data-testid={OnboardingSelectors.StepItemStart}
|
||||
_hover={{ textDecoration: 'none' }}
|
||||
fontSize="14px"
|
||||
mr="4px !important"
|
||||
onClick={onClick}
|
||||
>
|
||||
<Flex alignItems="center">
|
||||
<Tooltip
|
||||
disabled={routeType !== RouteType.External}
|
||||
label={externalLinkInfo}
|
||||
maxWidth="250px"
|
||||
placement="top"
|
||||
>
|
||||
<Flex alignItems="center">
|
||||
<Text color={color('accent')} fontWeight={500} mr="extra-tight">
|
||||
{action}
|
||||
</Text>
|
||||
<Box
|
||||
as={FiArrowRight}
|
||||
transform={routeType === RouteType.External ? 'rotate(-45deg)' : 'unset'}
|
||||
/>
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
</Link>
|
||||
)
|
||||
) : null}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
159
src/app/pages/home/components/onboarding-steps-list.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { FiX } from 'react-icons/fi';
|
||||
import { Circle, color, Flex, Grid, Stack } from '@stacks/ui';
|
||||
|
||||
import { HOME_FULL_PAGE_MAX_WIDTH } from '@app/components/global-styles/full-page-styles';
|
||||
import { useAnalytics } from '@app/common/hooks/analytics/use-analytics';
|
||||
import { openInNewTab } from '@app/common/utils/open-in-new-tab';
|
||||
import { SpaceBetween } from '@app/components/space-between';
|
||||
import { Text, Title } from '@app/components/typography';
|
||||
import { useAppDispatch } from '@app/store';
|
||||
import { onboardingActions } from '@app/store/onboarding/onboarding.actions';
|
||||
import { useStepsStatus } from '@app/store/onboarding/onboarding.selectors';
|
||||
import AddFundsFull from '@assets/images/onboarding/steps/add-funds-light.png';
|
||||
import BackUpSecretKeyFull from '@assets/images/onboarding/steps/backup-key-light.png';
|
||||
import ExploreAppsFull from '@assets/images/onboarding/steps/explore-apps-light.png';
|
||||
import BuyNftFull from '@assets/images/onboarding/steps/buy-nft-light.png';
|
||||
import AddFundsPopup from '@assets/images/onboarding/steps/add-funds-light-sm.png';
|
||||
import BackUpSecretKeyPopup from '@assets/images/onboarding/steps/backup-key-light-sm.png';
|
||||
import ExploreAppsPopup from '@assets/images/onboarding/steps/explore-apps-light-sm.png';
|
||||
import BuyNftPopup from '@assets/images/onboarding/steps/buy-nft-light-sm.png';
|
||||
import { OnboardingSteps, OnboardingStepStatus, RouteType } from '@shared/models/onboarding-types';
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding/onboarding.selectors';
|
||||
|
||||
import { OnboardingStepItem } from './onboarding-step-item';
|
||||
import { useOnboardingSteps } from '../hooks/use-onboarding-steps';
|
||||
|
||||
const onboardingSteps = [
|
||||
{
|
||||
action: 'View secret key',
|
||||
body: "Don't lose access to your account and crypto",
|
||||
event: 'back_up_secret_key',
|
||||
imageFull: BackUpSecretKeyFull,
|
||||
imagePopup: BackUpSecretKeyPopup,
|
||||
route: RouteUrls.ViewSecretKey,
|
||||
routeType: RouteType.Internal,
|
||||
title: OnboardingSteps.BackUpSecretKey,
|
||||
},
|
||||
{
|
||||
action: 'View options',
|
||||
body: 'Get some STX so you can start using apps',
|
||||
event: 'add_funds',
|
||||
imageFull: AddFundsFull,
|
||||
imagePopup: AddFundsPopup,
|
||||
route: RouteUrls.Buy,
|
||||
routeType: RouteType.Internal,
|
||||
title: OnboardingSteps.AddFunds,
|
||||
},
|
||||
{
|
||||
action: 'Find apps',
|
||||
body: 'Try Stacks apps for finance, NFTs, blogging and more',
|
||||
event: 'explore_apps',
|
||||
imageFull: ExploreAppsFull,
|
||||
imagePopup: ExploreAppsPopup,
|
||||
route: 'https://www.stacks.co/explore/discover-apps#apps',
|
||||
routeType: RouteType.External,
|
||||
title: OnboardingSteps.ExploreApps,
|
||||
},
|
||||
{
|
||||
action: 'Buy an NFT',
|
||||
body: 'Collect and trade NFTs secured by Bitcoin',
|
||||
event: 'buy_nft',
|
||||
imageFull: BuyNftFull,
|
||||
imagePopup: BuyNftPopup,
|
||||
route: 'https://stxnft.com/',
|
||||
routeType: RouteType.External,
|
||||
title: OnboardingSteps.BuyNft,
|
||||
},
|
||||
];
|
||||
|
||||
export function OnboardingStepsList() {
|
||||
const analytics = useAnalytics();
|
||||
const dispatch = useAppDispatch();
|
||||
const navigate = useNavigate();
|
||||
const onboardingStepsStatus = useStepsStatus();
|
||||
useOnboardingSteps();
|
||||
|
||||
const handleStepsDismiss = useCallback(() => {
|
||||
void analytics.track('dismiss_next_steps');
|
||||
dispatch(onboardingActions.hideSteps(true));
|
||||
}, [analytics, dispatch]);
|
||||
|
||||
const handleStepStart = useCallback(
|
||||
({ event, route, routeType, title }) => {
|
||||
if (title === OnboardingSteps.ExploreApps) {
|
||||
dispatch(
|
||||
onboardingActions.updateStepsStatus({
|
||||
...onboardingStepsStatus,
|
||||
[OnboardingSteps.ExploreApps]: OnboardingStepStatus.Done,
|
||||
})
|
||||
);
|
||||
}
|
||||
void analytics.track('select_next_step', { step: event });
|
||||
if (routeType === RouteType.Internal) navigate(route);
|
||||
else openInNewTab(route);
|
||||
},
|
||||
[analytics, dispatch, navigate, onboardingStepsStatus]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SpaceBetween
|
||||
maxWidth={['unset', HOME_FULL_PAGE_MAX_WIDTH]}
|
||||
mt={['tight', 'loose']}
|
||||
px={['base-loose', 'base-loose', 'base-loose', 'unset']}
|
||||
width="100%"
|
||||
>
|
||||
<Stack spacing="tight">
|
||||
<Text fontSize={[1, 2]}>Welcome to Stacks 👋</Text>
|
||||
<Title fontSize={[3, 4]}>Next steps for you</Title>
|
||||
</Stack>
|
||||
<Circle
|
||||
_hover={{ cursor: 'pointer' }}
|
||||
data-testid={OnboardingSelectors.HideStepsBtn}
|
||||
border="1px solid"
|
||||
borderColor={color('border')}
|
||||
color={color('bg')}
|
||||
onClick={() => handleStepsDismiss()}
|
||||
size="32px"
|
||||
>
|
||||
<FiX color={color('text-caption')} size="20px" />
|
||||
</Circle>
|
||||
</SpaceBetween>
|
||||
<Flex
|
||||
alignItems="center"
|
||||
borderBottom="3px solid"
|
||||
borderBottomColor={color('bg-4')}
|
||||
data-testid={OnboardingSelectors.StepsList}
|
||||
justifyContent={['start', 'center']}
|
||||
mt={['loose', 'extra-loose']}
|
||||
pb={['loose', '48px']}
|
||||
width="100%"
|
||||
>
|
||||
<Grid
|
||||
gap={[3, 5, 'unset']}
|
||||
maxWidth={['unset', HOME_FULL_PAGE_MAX_WIDTH]}
|
||||
px={['base-loose', 'base-loose', 'base-loose', 'unset']}
|
||||
templateColumns={['repeat(2, 2fr)', 'repeat(2, 2fr)', 'repeat(4, 1fr)']}
|
||||
width="100%"
|
||||
>
|
||||
{onboardingSteps.map(step => (
|
||||
<OnboardingStepItem
|
||||
action={step.action}
|
||||
body={step.body}
|
||||
imageFull={step.imageFull}
|
||||
imagePopup={step.imagePopup}
|
||||
isDone={onboardingStepsStatus[step.title] === OnboardingStepStatus.Done}
|
||||
key={step.title}
|
||||
onClick={() => handleStepStart(step)}
|
||||
routeType={step.routeType}
|
||||
title={step.title}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}
|
||||
10
src/app/pages/home/components/receive-button.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { ButtonProps } from '@stacks/ui';
|
||||
|
||||
import { QrCodeIcon } from '@app/components/qr-code-icon';
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
|
||||
import { TxButton } from './tx-button';
|
||||
|
||||
export const ReceiveButton = (props: ButtonProps) => (
|
||||
<TxButton icon={QrCodeIcon} route={RouteUrls.Receive} type="Receive" {...props} />
|
||||
);
|
||||
@@ -1,14 +1,27 @@
|
||||
import { memo, Suspense } from 'react';
|
||||
import { FiArrowUp } from 'react-icons/fi';
|
||||
import { ButtonProps } from '@stacks/ui';
|
||||
|
||||
import { useTransferableAssets } from '@app/store/assets/asset.hooks';
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
import { WalletPageSelectors } from '@tests/page-objects/wallet.selectors';
|
||||
|
||||
import { SendTxButton } from './tx-button';
|
||||
import { TxButton } from './tx-button';
|
||||
|
||||
const SendTxButton = (props: ButtonProps) => (
|
||||
<TxButton
|
||||
data-testid={WalletPageSelectors.BtnSendTokens}
|
||||
icon={FiArrowUp}
|
||||
route={RouteUrls.Send}
|
||||
type="Send"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
const SendButtonSuspense = () => {
|
||||
const assets = useTransferableAssets();
|
||||
const isDisabled = !assets || assets?.length === 0;
|
||||
return <SendTxButton isDisabled={isDisabled} data-testid={WalletPageSelectors.BtnSendTokens} />;
|
||||
return <SendTxButton isDisabled={isDisabled} />;
|
||||
};
|
||||
|
||||
const SendButtonFallback = memo(() => <SendTxButton isDisabled />);
|
||||
|
||||
@@ -1,90 +1,45 @@
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { FiArrowUp, FiPlus } from 'react-icons/fi';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Box, Button, ButtonProps } from '@stacks/ui';
|
||||
import { Box, ButtonProps, Text } from '@stacks/ui';
|
||||
|
||||
import { QrCodeIcon } from '@app/components/qr-code-icon';
|
||||
import { PrimaryButton } from '@app/components/primary-button';
|
||||
import { SecondaryButton } from '@app/components/secondary-button';
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
|
||||
export const SendTxButton = memo((props: ButtonProps) => {
|
||||
const ref = useRef<HTMLButtonElement | null>(null);
|
||||
interface TxButtonProps extends ButtonProps {
|
||||
icon: any;
|
||||
route: RouteUrls;
|
||||
type: string;
|
||||
}
|
||||
export const TxButton = memo((props: TxButtonProps) => {
|
||||
const { icon, route, type, ...rest } = props;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleClick = useCallback(() => navigate(RouteUrls.Send), [navigate]);
|
||||
const handleClick = useCallback(() => navigate(route), [navigate, route]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
size="sm"
|
||||
pl="base-tight"
|
||||
pr={'base'}
|
||||
py="tight"
|
||||
fontSize={2}
|
||||
mode="primary"
|
||||
position="relative"
|
||||
ref={ref}
|
||||
return type === 'Buy' ? (
|
||||
<SecondaryButton
|
||||
height="36px"
|
||||
onClick={handleClick}
|
||||
borderRadius="10px"
|
||||
{...props}
|
||||
>
|
||||
<Box as={FiArrowUp} transform={'unset'} size={'16px'} mr={0} />
|
||||
<Box as="span" ml="extra-tight" fontSize="14px">
|
||||
Send
|
||||
</Box>
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
|
||||
export const ReceiveTxButton: React.FC<ButtonProps> = memo(({ ...rest }) => {
|
||||
const ref = useRef<HTMLButtonElement | null>(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleClick = useCallback(() => navigate(RouteUrls.Receive), [navigate]);
|
||||
return (
|
||||
<Button
|
||||
size="sm"
|
||||
pl="base-tight"
|
||||
pr={'base'}
|
||||
px="base-tight"
|
||||
py="tight"
|
||||
fontSize={2}
|
||||
mode="primary"
|
||||
position="relative"
|
||||
ref={ref}
|
||||
onClick={handleClick}
|
||||
borderRadius="10px"
|
||||
{...rest}
|
||||
>
|
||||
<Box as={QrCodeIcon} transform="scaleY(-1)" size="14px" mr="2px" />
|
||||
<Box as="span" ml="extra-tight" fontSize="14px">
|
||||
Receive
|
||||
</Box>
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
|
||||
export const BuyTxButton: React.FC<ButtonProps> = memo(({ ...rest }) => {
|
||||
const ref = useRef<HTMLButtonElement | null>(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleClick = useCallback(() => navigate(RouteUrls.Buy), [navigate]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
size="sm"
|
||||
pl="base-tight"
|
||||
pr={'base'}
|
||||
py="tight"
|
||||
fontSize={2}
|
||||
mode="primary"
|
||||
position="relative"
|
||||
ref={ref}
|
||||
<Box as={icon} mr="tight" size="14px" />
|
||||
<Text fontSize="14px">{type}</Text>
|
||||
</SecondaryButton>
|
||||
) : (
|
||||
<PrimaryButton
|
||||
height="36px"
|
||||
onClick={handleClick}
|
||||
borderRadius="10px"
|
||||
px="base-tight"
|
||||
py="tight"
|
||||
position="relative"
|
||||
{...rest}
|
||||
>
|
||||
<Box as={FiPlus} transform={'scaleY(-1)'} size={'14px'} mr={'2px'} />
|
||||
<Box as="span" ml="extra-tight" fontSize="14px">
|
||||
Buy
|
||||
</Box>
|
||||
</Button>
|
||||
<Box as={icon} mr="tight" size="14px" />
|
||||
<Text fontSize="14px">{type}</Text>
|
||||
</PrimaryButton>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
import { Suspense, useEffect } from 'react';
|
||||
import { Outlet, useNavigate } from 'react-router-dom';
|
||||
import { Stack } from '@stacks/ui';
|
||||
import { Flex, Stack } from '@stacks/ui';
|
||||
|
||||
import { useRouteHeader } from '@app/common/hooks/use-route-header';
|
||||
import { useOnboardingState } from '@app/common/hooks/auth/use-onboarding-state';
|
||||
import { HOME_FULL_PAGE_MAX_WIDTH } from '@app/components/global-styles/full-page-styles';
|
||||
import { Header } from '@app/components/header';
|
||||
import { HiroMessages } from '@app/features/hiro-messages/hiro-messages';
|
||||
import { ActivityList } from '@app/features/activity-list/account-activity';
|
||||
import { BalancesList } from '@app/features/balances-list/balances-list';
|
||||
import { CurrentAccount } from '@app/pages/home/components/account-area';
|
||||
import { HomeActions } from '@app/pages/home/components/home-actions';
|
||||
import { useCurrentAccount } from '@app/store/accounts/account.hooks';
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
import { HomePageSelectors } from '@tests/page-objects/home-page.selectors';
|
||||
import { useCurrentAccount } from '@app/store/accounts/account.hooks';
|
||||
|
||||
import { AccountInfoFetcher, BalanceFetcher } from './components/fetchers';
|
||||
import { CENTERED_FULL_PAGE_MAX_WIDTH } from '@app/components/global-styles/full-page-styles';
|
||||
import { HomeTabs } from './components/home-tabs';
|
||||
import { OnboardingStepsList } from './components/onboarding-steps-list';
|
||||
import { useOnboardingSteps } from './hooks/use-onboarding-steps';
|
||||
|
||||
export function Home() {
|
||||
const { decodedAuthRequest } = useOnboardingState();
|
||||
const { showOnboardingSteps } = useOnboardingSteps();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const account = useCurrentAccount();
|
||||
|
||||
useRouteHeader(
|
||||
@@ -41,27 +44,36 @@ export function Home() {
|
||||
{account?.address && <BalanceFetcher address={account.address} />}
|
||||
{account?.address && <AccountInfoFetcher address={account.address} />}
|
||||
</Suspense>
|
||||
<Stack
|
||||
data-testid="home-page"
|
||||
flexGrow={1}
|
||||
maxWidth={CENTERED_FULL_PAGE_MAX_WIDTH}
|
||||
mt="loose"
|
||||
px={['unset', 'base-loose']}
|
||||
spacing="loose"
|
||||
>
|
||||
<CurrentAccount />
|
||||
<HomeActions />
|
||||
{account && (
|
||||
<HomeTabs
|
||||
balances={
|
||||
<BalancesList
|
||||
address={account?.address}
|
||||
data-testid={HomePageSelectors.BalancesList}
|
||||
/>
|
||||
}
|
||||
activity={<ActivityList />}
|
||||
/>
|
||||
)}
|
||||
<Stack alignItems="center" width="100%" spacing="extra-tight">
|
||||
{showOnboardingSteps && <OnboardingStepsList />}
|
||||
<Stack
|
||||
data-testid="home-page"
|
||||
maxWidth={['unset', HOME_FULL_PAGE_MAX_WIDTH]}
|
||||
mt="extra-loose"
|
||||
px={['base-loose', 'base-loose', 'base-loose', 'unset']}
|
||||
spacing="loose"
|
||||
width="100%"
|
||||
>
|
||||
<Flex
|
||||
flexDirection={['column', 'column', 'unset']}
|
||||
alignItems={['start', 'start', 'center']}
|
||||
justifyContent={['unset', 'space-between']}
|
||||
>
|
||||
<CurrentAccount />
|
||||
<HomeActions />
|
||||
</Flex>
|
||||
{account && (
|
||||
<HomeTabs
|
||||
balances={
|
||||
<BalancesList
|
||||
address={account?.address}
|
||||
data-testid={HomePageSelectors.BalancesList}
|
||||
/>
|
||||
}
|
||||
activity={<ActivityList />}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Outlet />
|
||||
</>
|
||||
|
||||
52
src/app/pages/home/hooks/use-onboarding-steps.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
|
||||
import {
|
||||
useAccounts,
|
||||
useCurrentAccountAvailableStxBalance,
|
||||
} from '@app/store/accounts/account.hooks';
|
||||
import { useAppDispatch } from '@app/store';
|
||||
import { useHideSteps, useStepsStatus } from '@app/store/onboarding/onboarding.selectors';
|
||||
import { onboardingActions } from '@app/store/onboarding/onboarding.actions';
|
||||
import { useCurrentAccountUnanchoredBalances } from '@app/query/balance/balance.hooks';
|
||||
import { OnboardingSteps, OnboardingStepStatus } from '@shared/models/onboarding-types';
|
||||
|
||||
export function useOnboardingSteps() {
|
||||
const dispatch = useAppDispatch();
|
||||
const hideOnboardingSteps = useHideSteps();
|
||||
const onboardingStepsStatus = useStepsStatus();
|
||||
const availableStxBalance = useCurrentAccountAvailableStxBalance();
|
||||
const { data: balances } = useCurrentAccountUnanchoredBalances();
|
||||
const accounts = useAccounts();
|
||||
|
||||
useEffect(() => {
|
||||
if (availableStxBalance?.isGreaterThan(0)) {
|
||||
dispatch(
|
||||
onboardingActions.updateStepsStatus({
|
||||
...onboardingStepsStatus,
|
||||
[OnboardingSteps.AddFunds]: OnboardingStepStatus.Done,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (balances && Object.keys(balances?.non_fungible_tokens).length > 0) {
|
||||
dispatch(
|
||||
onboardingActions.updateStepsStatus({
|
||||
...onboardingStepsStatus,
|
||||
[OnboardingSteps.BuyNft]: OnboardingStepStatus.Done,
|
||||
})
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [availableStxBalance, balances?.non_fungible_tokens]);
|
||||
|
||||
const hasCompletedOnboardingSteps = useMemo(() => {
|
||||
return Object.values(onboardingStepsStatus).every(val => val === OnboardingStepStatus.Done);
|
||||
}, [onboardingStepsStatus]);
|
||||
|
||||
const showOnboardingSteps =
|
||||
accounts?.length === 1 && !hasCompletedOnboardingSteps && !hideOnboardingSteps;
|
||||
|
||||
return {
|
||||
showOnboardingSteps,
|
||||
};
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
import { Box, Flex, Stack } from '@stacks/ui';
|
||||
import { Box, Flex, Stack, useMediaQuery } from '@stacks/ui';
|
||||
|
||||
import { Text } from '@app/components/typography';
|
||||
import { isFullPage, isPopup } from '@app/common/utils';
|
||||
import { CenteredPageContainer } from '@app/components/centered-page-container';
|
||||
import { CENTERED_FULL_PAGE_MAX_WIDTH } from '@app/components/global-styles/full-page-styles';
|
||||
import {
|
||||
CENTERED_FULL_PAGE_MAX_WIDTH,
|
||||
DESKTOP_VIEWPORT_MIN_WIDTH,
|
||||
ONBOARDING_PAGE_MAX_WIDTH,
|
||||
} from '@app/components/global-styles/full-page-styles';
|
||||
import { PageTitle } from '@app/components/page-title';
|
||||
import BackUpSecretKey from '@assets/images/onboarding/back-up-secret-key.svg';
|
||||
import BackUpSecretKey from '@assets/images/onboarding/back-up-secret-key.png';
|
||||
|
||||
import { BackUpSecretKeyActions } from './components/back-up-secret-key-actions';
|
||||
|
||||
@@ -16,9 +19,19 @@ interface BackUpSecretKeyLayoutProps {
|
||||
export function BackUpSecretKeyLayout(props: BackUpSecretKeyLayoutProps): JSX.Element {
|
||||
const { secretKeyDisplay, onBackedUpSecretKey } = props;
|
||||
|
||||
const [desktopViewport] = useMediaQuery(`(min-width: ${DESKTOP_VIEWPORT_MIN_WIDTH})`);
|
||||
|
||||
return (
|
||||
<CenteredPageContainer>
|
||||
<Stack isInline={isFullPage} pb="loose" width="100%">
|
||||
<Flex
|
||||
justifyContent="start"
|
||||
flexDirection={['column', 'column', 'unset']}
|
||||
maxWidth={ONBOARDING_PAGE_MAX_WIDTH}
|
||||
mt="tight"
|
||||
pb="loose"
|
||||
px="loose"
|
||||
width="100%"
|
||||
>
|
||||
<Flex
|
||||
alignItems={['start', 'center']}
|
||||
flexGrow={1}
|
||||
@@ -30,28 +43,30 @@ export function BackUpSecretKeyLayout(props: BackUpSecretKeyLayoutProps): JSX.El
|
||||
<img src={BackUpSecretKey} />
|
||||
</Box>
|
||||
<PageTitle>Back up your Secret Key</PageTitle>
|
||||
<Text>
|
||||
<Text maxWidth={['100%', '90%', '90%', '100%']}>
|
||||
Here’s your Secret Key: 24 words that generated your Stacks account. You’ll need it to
|
||||
access your account on a new device, in a different wallet, or in case you lose your
|
||||
password — so back it up somewhere safe.
|
||||
</Text>
|
||||
{isFullPage && <BackUpSecretKeyActions onBackedUpSecretKey={onBackedUpSecretKey} />}
|
||||
{desktopViewport && (
|
||||
<BackUpSecretKeyActions onBackedUpSecretKey={onBackedUpSecretKey} />
|
||||
)}
|
||||
</Stack>
|
||||
</Flex>
|
||||
<Flex
|
||||
alignItems={['start', 'center']}
|
||||
flexGrow={1}
|
||||
justifyContent="center"
|
||||
mt={['base', 'unset']}
|
||||
mt={['loose', 'loose', 'unset']}
|
||||
>
|
||||
<Box width={['344px', '446px']}>{secretKeyDisplay}</Box>
|
||||
</Flex>
|
||||
{isPopup && (
|
||||
{!desktopViewport && (
|
||||
<Stack mt="loose" spacing="loose">
|
||||
<BackUpSecretKeyActions onBackedUpSecretKey={onBackedUpSecretKey} />
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Flex>
|
||||
</CenteredPageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,8 @@ import { FiEyeOff, FiLock, FiRotateCcw } from 'react-icons/fi';
|
||||
import { Box, color, Stack } from '@stacks/ui';
|
||||
|
||||
import { Caption } from '@app/components/typography';
|
||||
import { isFullPage } from '@app/common/utils';
|
||||
import { PrimaryButton } from '@app/components/primary-button';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding.selectors';
|
||||
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding/onboarding.selectors';
|
||||
interface BackUpSecretKeyLayoutProps {
|
||||
onBackedUpSecretKey(): void;
|
||||
}
|
||||
@@ -26,14 +24,14 @@ export function BackUpSecretKeyActions(props: BackUpSecretKeyLayoutProps): JSX.E
|
||||
<Box as={FiLock} color={color('text-caption')} size="12px" />
|
||||
<Caption>Put it somewhere private and secure</Caption>
|
||||
</Stack>
|
||||
<Stack alignItems={isFullPage ? 'center' : 'start'} isInline={isFullPage} spacing="loose">
|
||||
<PrimaryButton
|
||||
data-testid={OnboardingSelectors.BackUpSecretKeyBtn}
|
||||
onClick={onBackedUpSecretKey}
|
||||
>
|
||||
I've backed it up
|
||||
</PrimaryButton>
|
||||
</Stack>
|
||||
|
||||
<PrimaryButton
|
||||
data-testid={OnboardingSelectors.BackUpSecretKeyBtn}
|
||||
onClick={onBackedUpSecretKey}
|
||||
width="170px"
|
||||
>
|
||||
I've backed it up
|
||||
</PrimaryButton>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,11 +20,17 @@ import { Header } from '@app/components/header';
|
||||
import { PageTitle } from '@app/components/page-title';
|
||||
import { CenteredPageContainer } from '@app/components/centered-page-container';
|
||||
import { CENTERED_FULL_PAGE_MAX_WIDTH } from '@app/components/global-styles/full-page-styles';
|
||||
import SetPassword from '@assets/images/onboarding/set-password.svg';
|
||||
import SetPassword from '@assets/images/onboarding/set-password.png';
|
||||
import { HUMAN_REACTION_DEBOUNCE_TIME } from '@shared/constants';
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
import { getWalletConfig } from '@shared/utils/wallet-config-helper';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding.selectors';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding/onboarding.selectors';
|
||||
|
||||
interface SetPasswordFormValues {
|
||||
password: string;
|
||||
confirmPassword: string;
|
||||
}
|
||||
const setPasswordFormValues: SetPasswordFormValues = { password: '', confirmPassword: '' };
|
||||
|
||||
export const SetPasswordPage = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -67,7 +73,7 @@ export const SetPasswordPage = () => {
|
||||
[wallet, setPassword, decodedAuthRequest, navigate, finishSignIn]
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
const onSubmit = useCallback(
|
||||
async ({ password }) => {
|
||||
if (!password) return;
|
||||
setLoading(true);
|
||||
@@ -109,8 +115,8 @@ export const SetPasswordPage = () => {
|
||||
return (
|
||||
<CenteredPageContainer>
|
||||
<Formik
|
||||
initialValues={{ password: '', confirmPassword: '' }}
|
||||
onSubmit={handleSubmit}
|
||||
initialValues={setPasswordFormValues}
|
||||
onSubmit={onSubmit}
|
||||
validationSchema={validationSchema}
|
||||
validateOnBlur={false}
|
||||
validateOnChange={false}
|
||||
@@ -121,6 +127,7 @@ export const SetPasswordPage = () => {
|
||||
<Stack
|
||||
maxWidth={CENTERED_FULL_PAGE_MAX_WIDTH}
|
||||
mb={['loose', 'unset']}
|
||||
px={['loose', 'unset']}
|
||||
spacing="loose"
|
||||
textAlign={['left', 'center']}
|
||||
>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import * as React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { validateMnemonic } from 'bip39';
|
||||
import toast from 'react-hot-toast';
|
||||
import { validateMnemonic } from 'bip39';
|
||||
|
||||
import {
|
||||
extractPhraseFromPasteEvent,
|
||||
@@ -15,6 +14,7 @@ import { useSeedInputErrorState } from '@app/store/onboarding/onboarding.hooks';
|
||||
import { useAnalytics } from '@app/common/hooks/analytics/use-analytics';
|
||||
import { useAppDispatch } from '@app/store';
|
||||
import { inMemoryKeyActions } from '@app/store/in-memory-key/in-memory-key.actions';
|
||||
import { onboardingActions } from '@app/store/onboarding/onboarding.actions';
|
||||
|
||||
async function simulateShortDelayToAvoidImmediateNavigation() {
|
||||
await delay(600);
|
||||
@@ -59,6 +59,7 @@ export function useSignIn() {
|
||||
if (result.isValid) {
|
||||
toast.success('Magic recovery code detected');
|
||||
await simulateShortDelayToAvoidImmediateNavigation();
|
||||
dispatch(onboardingActions.hideSteps(true));
|
||||
navigate({
|
||||
pathname: RouteUrls.MagicRecoveryCode,
|
||||
search: `?magicRecoveryCode=${parsedKeyInput}`,
|
||||
@@ -78,11 +79,12 @@ export function useSignIn() {
|
||||
await simulateShortDelayToAvoidImmediateNavigation();
|
||||
toast.success('Secret Key valid');
|
||||
dispatch(inMemoryKeyActions.saveUsersSecretKeyToBeRestored(parsedKeyInput));
|
||||
dispatch(onboardingActions.hideSteps(true));
|
||||
void analytics.track('submit_valid_secret_key');
|
||||
navigate(RouteUrls.SetPassword);
|
||||
setIsIdle();
|
||||
},
|
||||
[setIsLoading, handleSetError, navigate, dispatch, analytics, setIsIdle]
|
||||
[setIsLoading, dispatch, analytics, navigate, setIsIdle, handleSetError]
|
||||
);
|
||||
|
||||
const onPaste = useCallback(
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Text, Input, Stack, color, Box } from '@stacks/ui';
|
||||
import { Text, Input, Stack, color, Box, useMediaQuery } from '@stacks/ui';
|
||||
import { Form, Formik } from 'formik';
|
||||
|
||||
import { useRouteHeader } from '@app/common/hooks/use-route-header';
|
||||
import { isFullPage } from '@app/common/utils';
|
||||
import { ErrorLabel } from '@app/components/error-label';
|
||||
import { Header } from '@app/components/header';
|
||||
import { CenteredPageContainer } from '@app/components/centered-page-container';
|
||||
import { PrimaryButton } from '@app/components/primary-button';
|
||||
import { useSignIn } from '@app/pages/onboarding/sign-in/hooks/use-sign-in';
|
||||
import YourSecretKey from '@assets/images/onboarding/your-secret-key.svg';
|
||||
import YourSecretKey from '@assets/images/onboarding/your-secret-key.png';
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding.selectors';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding/onboarding.selectors';
|
||||
import { PageTitle } from '@app/components/page-title';
|
||||
import { Title } from '@app/components/typography';
|
||||
import { CENTERED_FULL_PAGE_MAX_WIDTH } from '@app/components/global-styles/full-page-styles';
|
||||
import {
|
||||
CENTERED_FULL_PAGE_MAX_WIDTH,
|
||||
DESKTOP_VIEWPORT_MIN_WIDTH,
|
||||
} from '@app/components/global-styles/full-page-styles';
|
||||
|
||||
export const SignIn = () => {
|
||||
const { onPaste, submitMnemonicForm, error, isLoading, ref } = useSignIn();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [desktopViewport] = useMediaQuery(`(min-width: ${DESKTOP_VIEWPORT_MIN_WIDTH})`);
|
||||
|
||||
useRouteHeader(<Header onClose={() => navigate(RouteUrls.Onboarding)} hideActions />);
|
||||
|
||||
return (
|
||||
@@ -32,14 +36,14 @@ export const SignIn = () => {
|
||||
<Form>
|
||||
<Stack
|
||||
maxWidth={CENTERED_FULL_PAGE_MAX_WIDTH}
|
||||
px={['unset', 'base-loose']}
|
||||
px={['loose', 'base-loose']}
|
||||
spacing={['loose', 'extra-loose']}
|
||||
textAlign={['left', 'center']}
|
||||
>
|
||||
<Box alignSelf={['start', 'center']} width={['81px', '101px']}>
|
||||
<img src={YourSecretKey} />
|
||||
</Box>
|
||||
{isFullPage ? (
|
||||
{desktopViewport ? (
|
||||
<PageTitle>Sign in with your Secret Key</PageTitle>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
import { Box, color, Flex, Stack } from '@stacks/ui';
|
||||
|
||||
import { isFullPage } from '@app/common/utils';
|
||||
import { ONBOARDING_PAGE_MAX_WIDTH } from '@app/components/global-styles/full-page-styles';
|
||||
import { Caption, Text } from '@app/components/typography';
|
||||
import { Link } from '@app/components/link';
|
||||
import { PageTitle } from '@app/components/page-title';
|
||||
import { PrimaryButton } from '@app/components/primary-button';
|
||||
import { CenteredPageContainer } from '@app/components/centered-page-container';
|
||||
import ExploreStacks from '@assets/images/onboarding/explore-stacks.svg';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding.selectors';
|
||||
import ExploreStacks from '@assets/images/onboarding/explore-stacks.png';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding/onboarding.selectors';
|
||||
|
||||
const WelcomeIllustration = () =>
|
||||
isFullPage ? (
|
||||
<Box bg={color('border')} borderRadius="16px" height="675px" width="518px">
|
||||
<Box left="43px" position="relative" top="92px">
|
||||
<img src={ExploreStacks} width="432px" />
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<Box bg={color('border')} borderRadius="16px" height="155px" overflow="hidden" width="344px">
|
||||
<Box position="relative" top="-138px">
|
||||
<img src={ExploreStacks} width="346px" />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
const WelcomeIllustration = () => (
|
||||
<Box
|
||||
aria-label="Abstract illustration highlighting crypto symbology"
|
||||
backgroundColor={color('border')}
|
||||
backgroundImage={`url(${ExploreStacks})`}
|
||||
backgroundPosition={[null, null, 'center']}
|
||||
backgroundPositionY={['-137px', '-187px', 'center']}
|
||||
backgroundRepeat="no-repeat"
|
||||
backgroundSize={['cover', null, null, '80%']}
|
||||
borderRadius="16px"
|
||||
height={['155px', '220px', '675px']}
|
||||
maxHeight="675px"
|
||||
maxWidth={['auto', 'auto', '518px']}
|
||||
overflow="hidden"
|
||||
width="100%"
|
||||
/>
|
||||
);
|
||||
|
||||
interface WelcomeLayoutProps {
|
||||
isGeneratingWallet: boolean;
|
||||
@@ -34,38 +37,58 @@ export function WelcomeLayout(props: WelcomeLayoutProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<CenteredPageContainer>
|
||||
<Stack isInline={isFullPage} width="100%">
|
||||
<Flex flexGrow={1} justifyContent="center" order={[0, 1, 1]}>
|
||||
<Flex
|
||||
justifyContent="start"
|
||||
flexDirection={['column', 'column', 'row-reverse']}
|
||||
maxWidth={ONBOARDING_PAGE_MAX_WIDTH}
|
||||
mt="tight"
|
||||
px="loose"
|
||||
width="100%"
|
||||
>
|
||||
<Flex flexGrow={1} justifyContent={['left', 'left', 'right']}>
|
||||
<WelcomeIllustration />
|
||||
</Flex>
|
||||
<Flex alignItems="center" flexGrow={1} justifyContent="center" mt={['base', 'unset']}>
|
||||
<Stack maxWidth="500px" spacing={['base', 'base-loose', 'loose']}>
|
||||
<PageTitle isHeadline>Explore the world of Stacks</PageTitle>
|
||||
<Text pr={['unset', '60px']}>
|
||||
Hiro Wallet connects you to Stacks apps while keeping your account, data, and crypto
|
||||
secure. Create your Stacks account to get started.
|
||||
</Text>
|
||||
<Flex
|
||||
alignItems="left"
|
||||
flexDirection="column"
|
||||
flexGrow="2"
|
||||
justifyContent="center"
|
||||
maxWidth="574px"
|
||||
mt={['base', 'unset']}
|
||||
>
|
||||
<PageTitle isHeadline maxWidth={['75%', '85%', '90%']} mt={['tight', 'base', 'loose']}>
|
||||
Explore the world of Stacks
|
||||
</PageTitle>
|
||||
<Text
|
||||
maxWidth={['unset', '90%']}
|
||||
mt={['base', null, null, 'extra-loose']}
|
||||
pr={['unset', '80px']}
|
||||
>
|
||||
Hiro Wallet connects you to Stacks apps while keeping your account, data, and crypto
|
||||
secure. Create your Stacks account to get started.
|
||||
</Text>
|
||||
<Box>
|
||||
<PrimaryButton
|
||||
data-testid={OnboardingSelectors.SignUpBtn}
|
||||
isLoading={isGeneratingWallet}
|
||||
mt={['base', null, 'loose']}
|
||||
onClick={onStartOnboarding}
|
||||
width="198px"
|
||||
>
|
||||
Create Stacks Account
|
||||
</PrimaryButton>
|
||||
<Stack mt={['base', 'base-tight', 'tight']} spacing="tight">
|
||||
<Caption>Already have a Stacks account?</Caption>
|
||||
<Link
|
||||
data-testid={OnboardingSelectors.SignInLink}
|
||||
fontSize="14px"
|
||||
onClick={onRestoreWallet}
|
||||
>
|
||||
Sign in with Secret Key
|
||||
</Link>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Stack mt="loose" spacing="tight">
|
||||
<Caption>Already have a Stacks account?</Caption>
|
||||
<Link
|
||||
data-testid={OnboardingSelectors.SignInLink}
|
||||
fontSize="14px"
|
||||
onClick={onRestoreWallet}
|
||||
>
|
||||
Sign in with Secret Key
|
||||
</Link>
|
||||
</Stack>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Flex>
|
||||
</CenteredPageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export const ReceiveTokens = () => {
|
||||
<CenteredPageContainer>
|
||||
<Stack
|
||||
maxWidth={CENTERED_FULL_PAGE_MAX_WIDTH}
|
||||
px={['unset', 'base-loose']}
|
||||
px={['loose', 'base-loose']}
|
||||
spacing="loose"
|
||||
textAlign="center"
|
||||
>
|
||||
|
||||
@@ -117,7 +117,7 @@ export function SendFormInner(props: SendFormInnerProps) {
|
||||
<Stack
|
||||
maxWidth={CENTERED_FULL_PAGE_MAX_WIDTH}
|
||||
mt="loose"
|
||||
px={['unset', 'base-loose']}
|
||||
px={['loose', 'base-loose']}
|
||||
spacing="loose"
|
||||
width="100%"
|
||||
>
|
||||
|
||||
@@ -92,7 +92,7 @@ function TransactionRequestBase(): JSX.Element | null {
|
||||
if (!handleBroadcastTransaction) return <UnauthorizedErrorMessage />;
|
||||
|
||||
return (
|
||||
<Stack spacing="loose">
|
||||
<Stack px={['loose', 'unset']} spacing="loose">
|
||||
<PageTop />
|
||||
<PostConditionModeWarning />
|
||||
<TransactionError />
|
||||
|
||||
@@ -17,7 +17,7 @@ import { useOnboardingState } from '@app/common/hooks/auth/use-onboarding-state'
|
||||
import { Text } from '@app/components/typography';
|
||||
import { useWaitingMessage, WaitingMessages } from '@app/common/utils/use-waiting-message';
|
||||
import { CENTERED_FULL_PAGE_MAX_WIDTH } from '@app/components/global-styles/full-page-styles';
|
||||
import UnlockSession from '@assets/images/unlock-session.svg';
|
||||
import UnlockSession from '@assets/images/unlock-session.png';
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
import { SettingsSelectors } from '@tests/integration/settings.selectors';
|
||||
|
||||
@@ -79,7 +79,7 @@ export function Unlock(): JSX.Element {
|
||||
<CenteredPageContainer>
|
||||
<Stack
|
||||
maxWidth={CENTERED_FULL_PAGE_MAX_WIDTH}
|
||||
px={['unset', 'base-loose']}
|
||||
px={['loose', 'base-loose']}
|
||||
spacing="loose"
|
||||
textAlign={['left', 'center']}
|
||||
width="100%"
|
||||
|
||||
@@ -30,11 +30,12 @@ export const ViewSecretKey = memo(() => {
|
||||
<Stack
|
||||
maxWidth={CENTERED_FULL_PAGE_MAX_WIDTH}
|
||||
pb={['loose', 'unset']}
|
||||
px={['unset', 'base-loose']}
|
||||
px={['loose', 'base-loose']}
|
||||
spacing="loose"
|
||||
textAlign={['left', 'center']}
|
||||
>
|
||||
<PageTitle fontSize={[4, 8]}>Your Secret Key</PageTitle>
|
||||
<Text color={color('text-caption')} textAlign={['left', 'center']}>
|
||||
<PageTitle fontSize={[4, 7]}>Your Secret Key</PageTitle>
|
||||
<Text color={color('text-caption')}>
|
||||
These 24 words are your Secret Key. They create your account, and you sign in on different
|
||||
devices with them. Make sure to save these somewhere safe.{' '}
|
||||
<Text display="inline" fontWeight={500}>
|
||||
|
||||
@@ -54,7 +54,9 @@ export function AppRoutes(): JSX.Element | null {
|
||||
path={RouteUrls.Home}
|
||||
element={
|
||||
<AccountGate>
|
||||
<Home />
|
||||
<Suspense fallback={<></>}>
|
||||
<Home />
|
||||
</Suspense>
|
||||
</AccountGate>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -18,6 +18,7 @@ import { stxChainSlice } from './chains/stx-chain.slice';
|
||||
import { broadcastActionTypeToOtherFramesMiddleware } from './utils/broadcast-action-types';
|
||||
import { inMemoryKeySlice } from './in-memory-key/in-memory-key.slice';
|
||||
import { ExtensionStorage } from './utils/extension-storage';
|
||||
import { onboardingSlice } from './onboarding/onboarding.slice';
|
||||
|
||||
const storage = new ExtensionStorage(chrome.storage.local, chrome.runtime);
|
||||
|
||||
@@ -27,6 +28,7 @@ const rootReducer = combineReducers({
|
||||
stx: stxChainSlice.reducer,
|
||||
}),
|
||||
inMemoryKeys: inMemoryKeySlice.reducer,
|
||||
onboarding: onboardingSlice.reducer,
|
||||
});
|
||||
|
||||
const persistConfig = {
|
||||
@@ -34,7 +36,7 @@ const persistConfig = {
|
||||
version: 1,
|
||||
storage,
|
||||
serialize: true,
|
||||
whitelist: ['keys', 'chains'],
|
||||
whitelist: ['keys', 'chains', 'onboarding'],
|
||||
};
|
||||
|
||||
const persistedReducer = persistReducer(persistConfig, rootReducer);
|
||||
|
||||
3
src/app/store/onboarding/onboarding.actions.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { onboardingSlice } from './onboarding.slice';
|
||||
|
||||
export const onboardingActions = onboardingSlice.actions;
|
||||
17
src/app/store/onboarding/onboarding.selectors.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { RootState } from '@app/store';
|
||||
|
||||
const selectOnboarding = (state: RootState) => state.onboarding;
|
||||
|
||||
const selectHideSteps = createSelector(selectOnboarding, state => state.hideSteps);
|
||||
const selectStepsStatus = createSelector(selectOnboarding, state => state.stepsStatus);
|
||||
|
||||
export function useHideSteps() {
|
||||
return useSelector(selectHideSteps);
|
||||
}
|
||||
|
||||
export function useStepsStatus() {
|
||||
return useSelector(selectStepsStatus);
|
||||
}
|
||||
35
src/app/store/onboarding/onboarding.slice.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
import {
|
||||
OnboardingSteps,
|
||||
OnboardingStepsStatus,
|
||||
OnboardingStepStatus,
|
||||
} from '@shared/models/onboarding-types';
|
||||
|
||||
interface OnboardingState {
|
||||
hideSteps: boolean;
|
||||
stepsStatus: OnboardingStepsStatus;
|
||||
}
|
||||
|
||||
const initialState: OnboardingState = {
|
||||
hideSteps: false,
|
||||
stepsStatus: {
|
||||
[OnboardingSteps.BackUpSecretKey]: OnboardingStepStatus.Done,
|
||||
[OnboardingSteps.AddFunds]: OnboardingStepStatus.Start,
|
||||
[OnboardingSteps.ExploreApps]: OnboardingStepStatus.Start,
|
||||
[OnboardingSteps.BuyNft]: OnboardingStepStatus.Start,
|
||||
},
|
||||
};
|
||||
|
||||
export const onboardingSlice = createSlice({
|
||||
name: 'onboarding',
|
||||
initialState,
|
||||
reducers: {
|
||||
hideSteps(state, action: PayloadAction<boolean>) {
|
||||
state.hideSteps = action.payload;
|
||||
},
|
||||
updateStepsStatus(state, action: PayloadAction<OnboardingStepsStatus>) {
|
||||
state.stepsStatus = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
const popupCenterWidth = 442;
|
||||
const popupCenterHeight = 646;
|
||||
import { POPUP_CENTER_HEIGHT, POPUP_CENTER_WIDTH } from '@shared/constants';
|
||||
|
||||
interface PopupOptions {
|
||||
url?: string;
|
||||
@@ -9,7 +8,7 @@ interface PopupOptions {
|
||||
skipPopupFallback?: boolean;
|
||||
}
|
||||
export function popupCenter(options: PopupOptions) {
|
||||
const { url, w = popupCenterWidth, h = popupCenterHeight } = options;
|
||||
const { url, w = POPUP_CENTER_WIDTH, h = POPUP_CENTER_HEIGHT } = options;
|
||||
|
||||
// @see https://developer.chrome.com/docs/extensions/reference/windows/#method-getCurrent
|
||||
chrome.windows.getCurrent(async win => {
|
||||
|
||||
@@ -2,6 +2,9 @@ import { ChainID } from '@stacks/transactions';
|
||||
|
||||
export const gaiaUrl = 'https://hub.blockstack.org';
|
||||
|
||||
export const POPUP_CENTER_WIDTH = 442;
|
||||
export const POPUP_CENTER_HEIGHT = 646;
|
||||
|
||||
export const HIGH_FEE_AMOUNT_STX = 5;
|
||||
|
||||
export const DEFAULT_FEE_RATE = 400;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
declare module '*.png';
|
||||
declare module '*.svg' {
|
||||
import React = require('react');
|
||||
export const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>;
|
||||
|
||||
23
src/shared/models/onboarding-types.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export enum OnboardingSteps {
|
||||
BackUpSecretKey = 'Back up secret key',
|
||||
AddFunds = 'Add some funds',
|
||||
ExploreApps = 'Explore apps',
|
||||
BuyNft = 'Buy an NFT',
|
||||
}
|
||||
|
||||
export enum OnboardingStepStatus {
|
||||
Start,
|
||||
Done,
|
||||
}
|
||||
|
||||
export const enum RouteType {
|
||||
Internal,
|
||||
External,
|
||||
}
|
||||
|
||||
export interface OnboardingStepsStatus {
|
||||
[OnboardingSteps.BackUpSecretKey]: OnboardingStepStatus;
|
||||
[OnboardingSteps.AddFunds]: OnboardingStepStatus;
|
||||
[OnboardingSteps.BuyNft]: OnboardingStepStatus;
|
||||
[OnboardingSteps.ExploreApps]: OnboardingStepStatus;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Button, Text, Box, ButtonGroup } from '@stacks/ui';
|
||||
import { useConnect } from '@stacks/connect-react';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding.selectors';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding/onboarding.selectors';
|
||||
|
||||
export const Auth: React.FC = () => {
|
||||
const { doOpenAuth } = useConnect();
|
||||
|
||||
@@ -2,12 +2,17 @@ export enum OnboardingSelectors {
|
||||
AnalyticsAllowBtn = 'analytics-allow-btn',
|
||||
AnalyticsDenyBtn = 'analytics-deny-btn',
|
||||
BackUpSecretKeyBtn = 'back-up-secret-key-btn',
|
||||
NewPasswordInput = 'set-or-enter-password-input',
|
||||
ConfirmPasswordInput = 'confirm-password-input',
|
||||
HideStepsBtn = 'hide-steps-btn',
|
||||
HiroWalletLogoRouteToHome = 'hiro-wallet-logo-route-to-home',
|
||||
NewPasswordInput = 'set-or-enter-password-input',
|
||||
NoAssetsFundAccountLink = 'no-assets-fund-account-link',
|
||||
SecretKey = 'secret-key',
|
||||
SetPasswordBtn = 'set-password-btn',
|
||||
SignInLink = 'sign-in-link',
|
||||
SignInBtn = 'sign-in-btn',
|
||||
SignUpBtn = 'sign-up-btn',
|
||||
HiroWalletLogoRouteToHome = 'hiro-wallet-logo-route-to-home',
|
||||
StepItemDone = 'step-item-done',
|
||||
StepItemStart = 'step-item-start',
|
||||
StepsList = 'steps-list',
|
||||
}
|
||||
@@ -22,8 +22,13 @@ describe(`Onboarding integration tests`, () => {
|
||||
} catch (error) {}
|
||||
});
|
||||
|
||||
it('should be able to sign up from welcome page', async () => {
|
||||
it('should be able to allow analytics', async () => {
|
||||
await wallet.clickAllowAnalytics();
|
||||
await wallet.waitForWelcomePage();
|
||||
});
|
||||
|
||||
it('should be able to sign up from welcome page', async () => {
|
||||
await wallet.clickDenyAnalytics();
|
||||
await wallet.clickSignUp();
|
||||
await wallet.backUpKeyAndSetPassword();
|
||||
await wallet.waitForHomePage();
|
||||
@@ -34,7 +39,7 @@ describe(`Onboarding integration tests`, () => {
|
||||
});
|
||||
|
||||
it('should be able to login from welcome page then logout', async () => {
|
||||
await wallet.clickAllowAnalytics();
|
||||
await wallet.clickDenyAnalytics();
|
||||
await wallet.clickSignIn();
|
||||
await wallet.loginWithPreviousSecretKey(SECRET_KEY);
|
||||
await wallet.waitForHomePage();
|
||||
@@ -46,11 +51,11 @@ describe(`Onboarding integration tests`, () => {
|
||||
await wallet.page.click('text=Sign Out');
|
||||
await wallet.page.click(wallet.$signOutConfirmHasBackupCheckbox);
|
||||
await wallet.page.click(wallet.$signOutDeleteWalletBtn);
|
||||
await wallet.waitForLoginPage();
|
||||
await wallet.waitForWelcomePage();
|
||||
});
|
||||
|
||||
it('should route to unlock page if the wallet is locked', async () => {
|
||||
await wallet.clickAllowAnalytics();
|
||||
await wallet.clickDenyAnalytics();
|
||||
await wallet.clickSignUp();
|
||||
await wallet.backUpKeyAndSetPassword();
|
||||
await wallet.waitForHomePage();
|
||||
@@ -78,4 +83,52 @@ describe(`Onboarding integration tests`, () => {
|
||||
const homePageVisible = await wallet.page.isVisible(wallet.$homePageBalancesList);
|
||||
expect(homePageVisible).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show onboarding steps for a new wallet with only one account', async () => {
|
||||
await wallet.clickDenyAnalytics();
|
||||
await wallet.clickSignUp();
|
||||
await wallet.backUpKeyAndSetPassword();
|
||||
await wallet.waitForHomePage();
|
||||
await wallet.waitForHideOnboardingsStepsButton();
|
||||
});
|
||||
|
||||
it('should hide onboarding steps when signing in with an existing secret key', async () => {
|
||||
await wallet.clickDenyAnalytics();
|
||||
await wallet.clickSignIn();
|
||||
await wallet.loginWithPreviousSecretKey(SECRET_KEY);
|
||||
await wallet.waitForHomePage();
|
||||
const onboardingStepsVisible = await wallet.page.isVisible(wallet.$onboardingStepsList);
|
||||
expect(onboardingStepsVisible).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should hide onboarding steps if user clicks the button to hide them', async () => {
|
||||
await wallet.clickDenyAnalytics();
|
||||
await wallet.clickSignUp();
|
||||
await wallet.backUpKeyAndSetPassword();
|
||||
await wallet.waitForHomePage();
|
||||
await wallet.waitForHideOnboardingsStepsButton();
|
||||
await wallet.clickHideSteps();
|
||||
});
|
||||
|
||||
it('should be able to start and complete an onboarding step in the list', async () => {
|
||||
await wallet.clickDenyAnalytics();
|
||||
await wallet.clickSignUp();
|
||||
await wallet.backUpKeyAndSetPassword();
|
||||
await wallet.waitForHomePage();
|
||||
await wallet.waitForOnboardingStepsList();
|
||||
const stepsToStartBtns = await wallet.page.$$(wallet.$onboardingStepStartBtn);
|
||||
await stepsToStartBtns[1].click();
|
||||
const stepsDone = await wallet.page.$$(wallet.$onboardingStepDoneBadge);
|
||||
expect(stepsDone.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should be able to use the link to add funds if balances list has no assets', async () => {
|
||||
await wallet.clickDenyAnalytics();
|
||||
await wallet.clickSignUp();
|
||||
await wallet.backUpKeyAndSetPassword();
|
||||
await wallet.waitForHomePage();
|
||||
await wallet.waitForOnboardingStepsList();
|
||||
const noAssetsFundAccountLink = await wallet.page.$(wallet.$noAssetsFundAccountLink);
|
||||
await noAssetsFundAccountLink?.click();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { createTestSelector, BrowserDriver, setupBrowser } from '../utils';
|
||||
import { WalletPage } from '../../page-objects/wallet.page';
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
import { validateStacksAddress } from '@app/common/stacks-utils';
|
||||
import { UserAreaSelectors } from '../user-area.selectors';
|
||||
|
||||
jest.setTimeout(30_000);
|
||||
jest.retryTimes(process.env.CI ? 2 : 0);
|
||||
describe(`Copy Address`, () => {
|
||||
let browser: BrowserDriver;
|
||||
let wallet: WalletPage;
|
||||
|
||||
const readClipboard = async () => {
|
||||
return await wallet.page.evaluate(() => window.navigator.clipboard.readText());
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
browser = await setupBrowser();
|
||||
wallet = await WalletPage.init(browser, RouteUrls.Onboarding);
|
||||
}, 10000);
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
await browser.context.close();
|
||||
} catch (error) {}
|
||||
});
|
||||
|
||||
it('should be able to copy address', async () => {
|
||||
await wallet.signUp();
|
||||
await wallet.page.click(createTestSelector(UserAreaSelectors.AccountBalancesCopyAddress));
|
||||
let copiedAddress = await readClipboard();
|
||||
expect(validateStacksAddress(copiedAddress)).toBeTruthy();
|
||||
|
||||
await wallet.page.click(createTestSelector(UserAreaSelectors.AccountCopyAddress));
|
||||
copiedAddress = await readClipboard();
|
||||
expect(validateStacksAddress(copiedAddress)).toBeTruthy();
|
||||
|
||||
await wallet.page.click('text=Receive');
|
||||
await wallet.page.click('text=Copy your address');
|
||||
|
||||
copiedAddress = await readClipboard();
|
||||
expect(validateStacksAddress(copiedAddress)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,8 @@
|
||||
import { BrowserDriver, createTestSelector, setupBrowser } from '../utils';
|
||||
import { WalletPage } from '../../page-objects/wallet.page';
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
import { SettingsSelectors } from '@tests/integration/settings.selectors';
|
||||
import { delay } from '@app/common/utils';
|
||||
import { WalletPage } from '../../page-objects/wallet.page';
|
||||
import { BrowserDriver, createTestSelector, setupBrowser } from '../utils';
|
||||
|
||||
jest.setTimeout(60_000);
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export enum UserAreaSelectors {
|
||||
AccountCopyAddress = 'account-copy-address',
|
||||
AccountBalancesCopyAddress = 'account-balances-copy-address',
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Page, BrowserContext } from 'playwright-core';
|
||||
import { createTestSelector } from '../integration/utils';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding.selectors';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding/onboarding.selectors';
|
||||
|
||||
export class DemoPage {
|
||||
static url = 'http://localhost:3000';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Page } from 'playwright-core';
|
||||
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding.selectors';
|
||||
import { OnboardingSelectors } from '@tests/integration/onboarding/onboarding.selectors';
|
||||
import { HomePageSelectors } from '@tests/page-objects/home-page.selectors';
|
||||
import { SettingsSelectors } from '@tests/integration/settings.selectors';
|
||||
import { BuyTokensSelectors } from '@tests/page-objects/buy-tokens-selectors';
|
||||
@@ -50,6 +50,11 @@ export class WalletPage {
|
||||
$enterPasswordInput = createTestSelector(SettingsSelectors.EnterPasswordInput);
|
||||
$unlockWalletBtn = createTestSelector(SettingsSelectors.UnlockWalletBtn);
|
||||
$magicRecoveryMessage = createTestSelector(WalletPageSelectors.MagicRecoveryMessage);
|
||||
$hideStepsBtn = createTestSelector(OnboardingSelectors.HideStepsBtn);
|
||||
$onboardingStepsList = createTestSelector(OnboardingSelectors.StepsList);
|
||||
$onboardingStepStartBtn = createTestSelector(OnboardingSelectors.StepItemStart);
|
||||
$onboardingStepDoneBadge = createTestSelector(OnboardingSelectors.StepItemDone);
|
||||
$noAssetsFundAccountLink = createTestSelector(OnboardingSelectors.NoAssetsFundAccountLink);
|
||||
|
||||
page: Page;
|
||||
|
||||
@@ -97,6 +102,10 @@ export class WalletPage {
|
||||
await this.page.click(this.$contractCallButton);
|
||||
}
|
||||
|
||||
async clickHideSteps() {
|
||||
await this.page.click(this.$hideStepsBtn);
|
||||
}
|
||||
|
||||
async waitForHomePage() {
|
||||
await this.page.waitForSelector(this.$homePageBalancesList, { timeout: 30000 });
|
||||
}
|
||||
@@ -121,7 +130,7 @@ export class WalletPage {
|
||||
await this.page.waitForSelector(this.$hiroWalletLogo, { timeout: 3000 });
|
||||
}
|
||||
|
||||
async waitForLoginPage() {
|
||||
async waitForWelcomePage() {
|
||||
await this.page.waitForSelector(this.$signInButton, { timeout: 3000 });
|
||||
}
|
||||
|
||||
@@ -129,6 +138,14 @@ export class WalletPage {
|
||||
await this.page.waitForSelector(this.$statusMessage, { timeout: 20000 });
|
||||
}
|
||||
|
||||
async waitForHideOnboardingsStepsButton() {
|
||||
await this.page.waitForSelector(this.$hideStepsBtn, { timeout: 30000 });
|
||||
}
|
||||
|
||||
async waitForOnboardingStepsList() {
|
||||
await this.page.waitForSelector(this.$onboardingStepsList, { timeout: 30000 });
|
||||
}
|
||||
|
||||
async loginWithPreviousSecretKey(secretKey: string) {
|
||||
await this.enterSecretKey(secretKey);
|
||||
await this.enterNewPassword();
|
||||
@@ -206,14 +223,14 @@ export class WalletPage {
|
||||
|
||||
/** Sign up with a randomly generated seed phrase */
|
||||
async signUp() {
|
||||
await this.clickAllowAnalytics();
|
||||
await this.clickDenyAnalytics();
|
||||
await this.clickSignUp();
|
||||
await this.backUpKeyAndSetPassword();
|
||||
await this.waitForHomePage();
|
||||
}
|
||||
|
||||
async signIn(secretKey: string) {
|
||||
await this.clickAllowAnalytics();
|
||||
await this.clickDenyAnalytics();
|
||||
await this.clickSignIn();
|
||||
let startTime = new Date();
|
||||
await this.enterSecretKey(secretKey);
|
||||
|
||||
@@ -123,6 +123,14 @@ const config = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
use: [
|
||||
|
||||