Compare commits

...
Author SHA1 Message Date
asiaznik a023f4248b fix: added vega wallet provider to test 2023-08-02 20:56:52 +02:00
asiaznik bb798d52f4 chore: view as disabled when connected 2023-08-02 19:05:49 +02:00
asiaznik 794863ca7f chore: tooltip, generic onClick 2023-08-02 19:05:20 +02:00
asiaznik 3cdde9cbae chore: remove xmlns 2023-08-02 19:05:20 +02:00
asiaznik f9ad9142bc chore: icon to vega icon 2023-08-02 19:05:20 +02:00
asiaznik 681eaf00d3 chore: reduced footprint 2023-08-02 19:05:20 +02:00
asiaznik 0bec5f58d7 feat(trading): view as button in sidebar 2023-08-02 19:05:20 +02:00
11 changed files with 178 additions and 48 deletions
@@ -1,7 +1,15 @@
import { act, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Sidebar, SidebarContent, ViewType, useSidebar } from './sidebar';
import {
Sidebar,
SidebarButton,
SidebarContent,
ViewType,
useSidebar,
} from './sidebar';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { VegaWalletProvider } from '@vegaprotocol/wallet';
jest.mock('../node-health', () => ({
NodeHealthContainer: () => <span data-testid="node-health" />,
@@ -28,11 +36,14 @@ describe('Sidebar', () => {
'does not render ticket and info',
(path) => {
render(
<MemoryRouter initialEntries={[path]}>
<Sidebar />
</MemoryRouter>
<VegaWalletProvider>
<MemoryRouter initialEntries={[path]}>
<Sidebar />
</MemoryRouter>
</VegaWalletProvider>
);
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument();
expect(screen.getByTestId(ViewType.Settings)).toBeInTheDocument();
expect(screen.getByTestId(ViewType.Transfer)).toBeInTheDocument();
expect(screen.getByTestId(ViewType.Deposit)).toBeInTheDocument();
@@ -47,11 +58,14 @@ describe('Sidebar', () => {
it('renders ticket and info on market pages', () => {
render(
<MemoryRouter initialEntries={['/markets/ABC']}>
<Sidebar />
</MemoryRouter>
<VegaWalletProvider>
<MemoryRouter initialEntries={['/markets/ABC']}>
<Sidebar />
</MemoryRouter>
</VegaWalletProvider>
);
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument();
expect(screen.getByTestId(ViewType.Settings)).toBeInTheDocument();
expect(screen.getByTestId(ViewType.Transfer)).toBeInTheDocument();
expect(screen.getByTestId(ViewType.Deposit)).toBeInTheDocument();
@@ -65,9 +79,11 @@ describe('Sidebar', () => {
it('renders selected state', async () => {
render(
<MemoryRouter initialEntries={['/markets/ABC']}>
<Sidebar />
</MemoryRouter>
<VegaWalletProvider>
<MemoryRouter initialEntries={['/markets/ABC']}>
<Sidebar />
</MemoryRouter>
</VegaWalletProvider>
);
const settingsButton = screen.getByTestId(ViewType.Settings);
@@ -91,11 +107,13 @@ describe('Sidebar', () => {
describe('SidebarContent', () => {
it('renders the correct content', () => {
const { container } = render(
<MemoryRouter initialEntries={['/markets/ABC']}>
<Routes>
<Route path="/markets/:marketId" element={<SidebarContent />} />
</Routes>
</MemoryRouter>
<VegaWalletProvider>
<MemoryRouter initialEntries={['/markets/ABC']}>
<Routes>
<Route path="/markets/:marketId" element={<SidebarContent />} />
</Routes>
</MemoryRouter>
</VegaWalletProvider>
);
expect(container).toBeEmptyDOMElement();
@@ -115,11 +133,13 @@ describe('SidebarContent', () => {
it('closes sidebar if market id is required but not present', () => {
const { container } = render(
<MemoryRouter initialEntries={['/portfolio']}>
<Routes>
<Route path="/portfolio" element={<SidebarContent />} />
</Routes>
</MemoryRouter>
<VegaWalletProvider>
<MemoryRouter initialEntries={['/portfolio']}>
<Routes>
<Route path="/portfolio" element={<SidebarContent />} />
</Routes>
</MemoryRouter>
</VegaWalletProvider>
);
act(() => {
@@ -141,3 +161,25 @@ describe('SidebarContent', () => {
expect(container).toBeEmptyDOMElement();
});
});
describe('SidebarButton', () => {
it.each([ViewType.Info, ViewType.Deposit, ViewType.ViewAs])(
'runs given callback regardless of requested view (%s)',
async (view) => {
const onClick = jest.fn();
render(
<SidebarButton
icon={VegaIconNames.INFO}
tooltip="INFO"
onClick={onClick}
view={view}
/>
);
const btn = screen.getByTestId(view);
await userEvent.click(btn);
expect(onClick).toBeCalled();
}
);
});
+37 -12
View File
@@ -16,6 +16,7 @@ import { WithdrawContainer } from '../withdraw-container';
import { Routes as AppRoutes } from '../../pages/client-router';
import { persist } from 'zustand/middleware';
import { GetStarted } from '../welcome-dialog';
import { useVegaWallet, useViewAsDialog } from '@vegaprotocol/wallet';
const STORAGE_KEY = 'vega_sidebar_store';
@@ -26,6 +27,7 @@ export enum ViewType {
Withdraw = 'Withdraw',
Transfer = 'Transfer',
Settings = 'Settings',
ViewAs = 'ViewAs',
}
type SidebarView =
@@ -53,6 +55,8 @@ type SidebarView =
export const Sidebar = () => {
const navClasses = 'flex lg:flex-col items-center gap-2 lg:gap-4 p-1';
const setViewAsDialogOpen = useViewAsDialog((state) => state.setOpen);
const { pubKeys } = useVegaWallet();
return (
<div className="flex lg:flex-col gap-2 h-full p-1" data-testid="sidebar">
<nav className={navClasses}>
@@ -105,6 +109,16 @@ export const Sidebar = () => {
</Routes>
</nav>
<nav className={classNames(navClasses, 'ml-auto lg:mt-auto lg:ml-0')}>
<SidebarButton
view={ViewType.ViewAs}
onClick={() => {
setViewAsDialogOpen(true);
}}
icon={VegaIconNames.EYE}
tooltip={t('View as party')}
disabled={Boolean(pubKeys)}
/>
<SidebarButton
view={ViewType.Settings}
icon={VegaIconNames.COG}
@@ -116,25 +130,41 @@ export const Sidebar = () => {
);
};
const SidebarButton = ({
export const SidebarButton = ({
view,
icon,
tooltip,
disabled = false,
onClick,
}: {
view: ViewType;
view?: ViewType;
icon: VegaIconNames;
tooltip: string;
disabled?: boolean;
onClick?: () => void;
}) => {
const { currView, setView } = useSidebar((store) => ({
currView: store.view,
setView: store.setView,
}));
const onSelect = (view: SidebarView['type']) => {
if (view === currView?.type) {
setView(null);
} else {
setView({ type: view });
}
};
const buttonClasses = classNames('flex items-center p-1 rounded', {
'text-vega-clight-200 dark:text-vega-cdark-200 hover:bg-vega-clight-500 dark:hover:bg-vega-cdark-500':
view !== currView?.type,
!view || view !== currView?.type,
'bg-vega-yellow hover:bg-vega-yellow-550 text-black':
view === currView?.type,
view && view === currView?.type,
'cursor-not-allowed text-vega-clight-500 hover:bg-inherit dark:text-vega-cdark-500 dark:hover:bg-inherit':
disabled,
});
return (
<Tooltip
description={tooltip}
@@ -146,13 +176,8 @@ const SidebarButton = ({
<button
className={buttonClasses}
data-testid={view}
onClick={() => {
if (view === currView?.type) {
setView(null);
} else {
setView({ type: view });
}
}}
onClick={onClick || (() => onSelect(view as SidebarView['type']))}
disabled={disabled}
>
<VegaIcon name={icon} size={20} />
</button>
@@ -282,7 +307,7 @@ export const useSidebar = create<{
view: null,
setView: (x) =>
set(() => {
if (x === null) {
if (x == null) {
return { view: null, init: false };
}
+2 -1
View File
@@ -2,7 +2,7 @@ import {
AssetDetailsDialog,
useAssetDetailsDialogStore,
} from '@vegaprotocol/assets';
import { VegaConnectDialog } from '@vegaprotocol/wallet';
import { VegaConnectDialog, ViewAsDialog } from '@vegaprotocol/wallet';
import { Connectors } from '../lib/vega-connectors';
import {
Web3ConnectUncontrolledDialog,
@@ -19,6 +19,7 @@ const DialogsContainer = () => {
connectors={Connectors}
riskMessage={<RiskMessage />}
/>
<ViewAsDialog connector={Connectors.view} />
<AssetDetailsDialog
assetId={id}
trigger={trigger || null}
@@ -0,0 +1,8 @@
export const IconEye = ({ size = 16 }: { size: number }) => {
return (
<svg width={size} height={size} viewBox="0 0 16 16">
<path d="M0.678395 7.44128C2.71173 4.52663 5.34817 2.99829 7.9894 2.99829C11.0914 2.99829 13.7635 5.01915 15.34 7.45404L15.3408 7.45519C15.4451 7.61729 15.5006 7.80599 15.5006 7.99876C15.5006 8.19118 15.4453 8.37954 15.3414 8.54144C13.7662 11.0077 11.1114 12.9983 7.9894 12.9983C4.8342 12.9983 2.23054 11.0118 0.659261 8.55277C0.552495 8.38698 0.497119 8.19332 0.500115 7.99613C0.503119 7.79845 0.564651 7.6061 0.676935 7.44338L0.67839 7.44127L0.678395 7.44128ZM1.5 8.01133L1.50139 8.01348L1.50139 8.01349C2.95267 10.2852 5.27875 11.9983 7.9894 11.9983C10.6697 11.9983 13.0459 10.278 14.4989 8.00262L14.4999 8.00113C14.5001 8.00073 14.5003 8.0003 14.5005 7.99985C14.5005 7.9995 14.5006 7.99913 14.5006 7.99876C14.5006 7.99792 14.5003 7.9971 14.4999 7.99639L14.9203 7.72579L14.5006 7.99754C13.0421 5.74493 10.6461 3.99829 7.9894 3.99829C5.75997 3.99829 3.40017 5.28866 1.5 8.01133Z" />
<path d="M8 5.99951C6.89543 5.99951 6 6.89494 6 7.99951C6 9.10408 6.89543 9.99951 8 9.99951C9.10457 9.99951 10 9.10408 10 7.99951C10 6.89494 9.10457 5.99951 8 5.99951ZM5 7.99951C5 6.34266 6.34315 4.99951 8 4.99951C9.65685 4.99951 11 6.34266 11 7.99951C11 9.65637 9.65685 10.9995 8 10.9995C6.34315 10.9995 5 9.65637 5 7.99951Z" />
</svg>
);
};
@@ -12,6 +12,7 @@ import { IconCross } from './svg-icons/icon-cross';
import { IconDeposit } from './svg-icons/icon-deposit';
import { IconEdit } from './svg-icons/icon-edit';
import { IconExclaimationMark } from './svg-icons/icon-exclaimation-mark';
import { IconEye } from './svg-icons/icon-eye';
import { IconForum } from './svg-icons/icon-forum';
import { IconGlobe } from './svg-icons/icon-globe';
import { IconInfo } from './svg-icons/icon-info';
@@ -48,6 +49,7 @@ export enum VegaIconNames {
DEPOSIT = 'deposit',
EDIT = 'edit',
EXCLAIMATION_MARK = 'exclaimation-mark',
EYE = 'eye',
FORUM = 'forum',
GLOBE = 'globe',
INFO = 'info',
@@ -88,6 +90,7 @@ export const VegaIconNameMap: Record<
deposit: IconDeposit,
edit: IconEdit,
'exclaimation-mark': IconExclaimationMark,
eye: IconEye,
forum: IconForum,
globe: IconGlobe,
info: IconInfo,
@@ -21,7 +21,7 @@ export const NotificationBanner = ({
return (
<div
className={classNames(
'flex items-center p-3 border-b min-h-[56px]',
'flex items-center px-1 py-3 border-b min-h-[56px]',
'text-[12px] leading-[16px] font-normal',
{
'bg-vega-light-100 dark:bg-vega-dark-100 ': intent === Intent.None,
@@ -8,7 +8,7 @@ import type {
import { Intent } from '../../utils/intent';
type TradingButtonProps = {
size?: 'large' | 'medium' | 'small';
size?: 'large' | 'medium' | 'small' | 'extra-small';
intent?: Intent;
children?: ReactNode;
icon?: ReactNode;
@@ -31,6 +31,7 @@ const getClassName = (
'h-10': !subLabel && (!size || size === 'medium'),
'h-8': !subLabel && size === 'small',
'px-3 text-sm': !subLabel && size === 'small',
'h-6 px-2 text-xs': !subLabel && size === 'extra-small',
'px-4 text-base': !subLabel && size !== 'small',
'flex-col items-center justify-center px-3 pt-2.5 pb-2': subLabel,
},
@@ -1,6 +1,7 @@
import { t } from '@vegaprotocol/i18n';
import { NotificationBanner } from '../notification-banner';
import { Intent } from '../../utils/intent';
import { TradingButton } from '../trading-button';
export function truncateMiddle(address: string, start = 6, end = 4) {
if (address.length < 11) return address;
@@ -24,19 +25,20 @@ export const ViewingAsBanner = ({
<NotificationBanner
data-testid="view-banner"
intent={Intent.None}
className="py-2 min-h-fit"
className="!px-1 !py-1 min-h-fit"
>
<div className="flex justify-between items-baseline">
<span>
{t('Viewing as Vega user:')} {pubKey && truncateMiddle(pubKey)}{' '}
</span>
<button
className="p-2 bg-light-dark-150 dark:bg-vega-dark-150 rounded uppercase"
<TradingButton
intent={Intent.None}
size="extra-small"
data-testid="exit-view"
onClick={disconnect}
>
{t('Exit view as')}
</button>
</TradingButton>
</div>
</NotificationBanner>
);
+1
View File
@@ -1 +1,2 @@
export * from './connect-dialog';
export * from './view-as-dialog';
@@ -0,0 +1,40 @@
import { Dialog } from '@vegaprotocol/ui-toolkit';
import { ViewConnectorForm } from './view-connector-form';
import type { ViewConnector } from '../connectors';
import { create } from 'zustand';
type ViewAsDialogStore = {
open: boolean;
setOpen: (open: boolean) => void;
};
export const useViewAsDialog = create<ViewAsDialogStore>()((set) => ({
open: false,
setOpen: (open) => set({ open }),
}));
type ViewAsDialogProps = {
connector: ViewConnector;
};
export const ViewAsDialog = ({ connector }: ViewAsDialogProps) => {
const open = useViewAsDialog((state) => state.open);
const setOpen = useViewAsDialog((state) => state.setOpen);
return (
<Dialog
open={open}
size="small"
onChange={(open) => {
setOpen(open);
}}
>
<ViewConnectorForm
connector={connector}
onConnect={() => {
setOpen(false);
}}
/>
</Dialog>
);
};
@@ -2,9 +2,10 @@ import { t } from '@vegaprotocol/i18n';
import {
Button,
FormGroup,
Icon,
Input,
InputError,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { useForm } from 'react-hook-form';
import type { ViewConnector } from '../connectors';
@@ -17,7 +18,7 @@ interface FormFields {
interface RestConnectorFormProps {
connector: ViewConnector;
onConnect: (connector: ViewConnector) => void;
reset: () => void;
reset?: () => void;
}
export function ViewConnectorForm({
@@ -50,13 +51,19 @@ export function ViewConnectorForm({
return (
<>
<button
onClick={reset}
className="absolute p-2 top-0 left-0 md:top-2 md:left-2"
data-testid="back-button"
>
<Icon name={'chevron-left'} ariaLabel="back" size={4} />
</button>
{reset && (
<button
onClick={reset}
className="absolute p-2 top-0 left-0 md:top-2 md:left-2"
data-testid="back-button"
>
<VegaIcon
name={VegaIconNames.CHEVRON_LEFT}
aria-label="back"
size={16}
/>
</button>
)}
<form onSubmit={handleSubmit(onSubmit)} data-testid="view-connector-form">
<h1 className="text-2xl uppercase mb-6 text-center font-alpha calt">
{t('VIEW AS VEGA USER')}