Add setting custom cluster endpoint
This commit is contained in:
parent
d332181ae8
commit
2b5e310cdc
|
@ -0,0 +1,62 @@
|
|||
import React, {useState} from "react";
|
||||
import {Col, Input, Modal, Row} from "antd";
|
||||
import {EndpointInfo} from "../utils/types";
|
||||
|
||||
export default function CustomClusterEndpointDialog({
|
||||
visible,
|
||||
onAddCustomEndpoint,
|
||||
onClose,
|
||||
} : {
|
||||
visible: boolean;
|
||||
onAddCustomEndpoint: (info: EndpointInfo) => void;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const [ customEndpoint, setCustomEndpoint] = useState('');
|
||||
const [ customEndpointName, setCustomEndpointName] = useState('');
|
||||
|
||||
const onSubmit = () => {
|
||||
const params = {
|
||||
name: customEndpointName,
|
||||
endpoint: customEndpoint,
|
||||
custom: true,
|
||||
}
|
||||
onAddCustomEndpoint(params);
|
||||
onDoClose();
|
||||
};
|
||||
const onDoClose = () => {
|
||||
setCustomEndpoint('')
|
||||
setCustomEndpointName('')
|
||||
onClose && onClose();
|
||||
};
|
||||
const canSubmit = customEndpoint !== '' && customEndpointName !== '';
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={'Add custom endpoint'}
|
||||
visible={visible}
|
||||
onOk={onSubmit}
|
||||
okText={'Add'}
|
||||
onCancel={onDoClose}
|
||||
okButtonProps={{ disabled: !canSubmit }}
|
||||
>
|
||||
<Row style={{ marginBottom: 8 }}>
|
||||
<Col span={24}>
|
||||
<Input
|
||||
placeholder="Cluster Name"
|
||||
value={customEndpointName}
|
||||
onChange={(e) => setCustomEndpointName(e.target.value)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{ marginBottom: 8 }}>
|
||||
<Col span={24}>
|
||||
<Input
|
||||
placeholder="Cluster Endpoint"
|
||||
value={customEndpoint}
|
||||
onChange={(e) => setCustomEndpoint(e.target.value)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Modal>
|
||||
);
|
||||
}
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { Button } from 'antd';
|
||||
import { LinkOutlined } from '@ant-design/icons';
|
||||
|
||||
export default function LinkAddress({ title, address }) {
|
||||
export default function LinkAddress({ title, address }: {title?: undefined | any; address: string;}) {
|
||||
return (
|
||||
<div>
|
||||
{title && <p style={{ color: 'white' }}>{title}</p>}
|
|
@ -1,131 +0,0 @@
|
|||
import {
|
||||
InfoCircleOutlined,
|
||||
SettingOutlined,
|
||||
UserOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Button, Menu, Popover, Select } from 'antd';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import logo from '../assets/logo.svg';
|
||||
import styled from 'styled-components';
|
||||
import { useWallet, WALLET_PROVIDERS } from '../utils/wallet';
|
||||
import { ENDPOINTS, useConnectionConfig } from '../utils/connection';
|
||||
import LinkAddress from './LinkAddress';
|
||||
import Settings from './Settings';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
background-color: #0d1017;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
padding: 0px 30px;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
const LogoWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #2abdd2;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
img {
|
||||
height: 30px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default function TopBar() {
|
||||
const { connected, wallet, providerUrl, setProvider } = useWallet();
|
||||
const { endpoint, setEndpoint } = useConnectionConfig();
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
|
||||
const publicKey = wallet?.publicKey?.toBase58();
|
||||
|
||||
const handleClick = useCallback(
|
||||
(e) => {
|
||||
history.push(e.key);
|
||||
},
|
||||
[history],
|
||||
);
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<LogoWrapper>
|
||||
<img src={logo} alt="" onClick={() => history.push('/')} />
|
||||
{'SERUM'}
|
||||
</LogoWrapper>
|
||||
<Menu
|
||||
mode="horizontal"
|
||||
onClick={handleClick}
|
||||
selectedKeys={[location.pathname]}
|
||||
style={{
|
||||
borderBottom: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end',
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<Menu.Item key="/">TRADE</Menu.Item>
|
||||
</Menu>
|
||||
{connected && (
|
||||
<div>
|
||||
<Popover
|
||||
content={<Settings autoApprove={wallet?.autoApprove} />}
|
||||
placement="bottomRight"
|
||||
title="Settings"
|
||||
trigger="click"
|
||||
>
|
||||
<Button style={{ marginRight: 8 }}>
|
||||
<SettingOutlined />
|
||||
Settings
|
||||
</Button>
|
||||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Select
|
||||
onSelect={setEndpoint}
|
||||
value={endpoint}
|
||||
style={{ marginRight: 8 }}
|
||||
>
|
||||
{ENDPOINTS.map(({ name, endpoint }) => (
|
||||
<Select.Option value={endpoint} key={endpoint}>
|
||||
{name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Select onSelect={setProvider} value={providerUrl}>
|
||||
{WALLET_PROVIDERS.map(({ name, url }) => (
|
||||
<Select.Option value={url} key={url}>
|
||||
{name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
type="text"
|
||||
size="large"
|
||||
onClick={connected ? wallet.disconnect : wallet.connect}
|
||||
style={{ color: '#2abdd2' }}
|
||||
>
|
||||
<UserOutlined />
|
||||
{!connected ? 'Connect wallet' : 'Disconnect'}
|
||||
</Button>
|
||||
{connected && (
|
||||
<Popover
|
||||
content={<LinkAddress address={publicKey} />}
|
||||
placement="bottomRight"
|
||||
title="Wallet public key"
|
||||
trigger="click"
|
||||
>
|
||||
<InfoCircleOutlined style={{ color: '#2abdd2' }} />
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
import {InfoCircleOutlined, PlusCircleOutlined, SettingOutlined, UserOutlined,} from '@ant-design/icons';
|
||||
import {Button, Col, Menu, Popover, Row, Select} from 'antd';
|
||||
import React, {useCallback, useState} from 'react';
|
||||
import {useHistory, useLocation} from 'react-router-dom';
|
||||
import logo from '../assets/logo.svg';
|
||||
import styled from 'styled-components';
|
||||
import {useWallet, WALLET_PROVIDERS} from '../utils/wallet';
|
||||
import {useConnectionConfig} from '../utils/connection';
|
||||
import LinkAddress from './LinkAddress';
|
||||
import Settings from './Settings';
|
||||
import CustomClusterEndpointDialog from "./CustomClusterEndpointDialog";
|
||||
import {EndpointInfo} from "../utils/types";
|
||||
import {notify} from "../utils/notifications";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
background-color: #0d1017;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
padding: 0px 30px;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
const LogoWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #2abdd2;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
img {
|
||||
height: 30px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default function TopBar() {
|
||||
const { connected, wallet, providerUrl, setProvider } = useWallet();
|
||||
const { endpoint, setEndpoint, availableEndpoints, setCustomEndpoints } = useConnectionConfig();
|
||||
const [ addEndpointVisible, setAddEndpointVisible ] = useState(false)
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
|
||||
const publicKey = wallet?.publicKey?.toBase58();
|
||||
|
||||
const handleClick = useCallback(
|
||||
(e) => {
|
||||
history.push(e.key);
|
||||
},
|
||||
[history],
|
||||
);
|
||||
|
||||
const onAddCustomEndpoint = (info: EndpointInfo) => {
|
||||
const existingEndpoint = availableEndpoints.some(
|
||||
(e) => e.endpoint === info.endpoint,
|
||||
);
|
||||
if (existingEndpoint) {
|
||||
notify({
|
||||
message: `An endpoint with the given url already exists`,
|
||||
type: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const newCustomEndpoints = [...availableEndpoints.filter(e => e.custom), info];
|
||||
setEndpoint(info.name);
|
||||
setCustomEndpoints(newCustomEndpoints);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomClusterEndpointDialog
|
||||
visible={addEndpointVisible}
|
||||
onAddCustomEndpoint={onAddCustomEndpoint}
|
||||
onClose={() => setAddEndpointVisible(false)}
|
||||
/>
|
||||
<Wrapper>
|
||||
<LogoWrapper>
|
||||
<img src={logo} alt="" onClick={() => history.push('/')} />
|
||||
{'SERUM'}
|
||||
</LogoWrapper>
|
||||
<Menu
|
||||
mode="horizontal"
|
||||
onClick={handleClick}
|
||||
selectedKeys={[location.pathname]}
|
||||
style={{
|
||||
borderBottom: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end',
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<Menu.Item key="/">TRADE</Menu.Item>
|
||||
</Menu>
|
||||
<div>
|
||||
<Row
|
||||
align="middle"
|
||||
style={{ paddingLeft: 5, paddingRight: 5 }}
|
||||
gutter={16}
|
||||
>
|
||||
<Col>
|
||||
<PlusCircleOutlined
|
||||
style={{ color: '#2abdd2' }}
|
||||
onClick={() => setAddEndpointVisible(true)}
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<Popover
|
||||
content={endpoint}
|
||||
placement="bottomRight"
|
||||
title="URL"
|
||||
trigger="hover"
|
||||
>
|
||||
<InfoCircleOutlined style={{ color: '#2abdd2' }} />
|
||||
</Popover>
|
||||
</Col>
|
||||
<Col>
|
||||
<Select
|
||||
onSelect={setEndpoint}
|
||||
value={endpoint}
|
||||
style={{ marginRight: 8, width: '150px' }}
|
||||
>
|
||||
{availableEndpoints.map(({ name, endpoint }) => (
|
||||
<Select.Option value={endpoint} key={endpoint}>
|
||||
{name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
{connected && (
|
||||
<div>
|
||||
<Popover
|
||||
content={<Settings autoApprove={wallet?.autoApprove} />}
|
||||
placement="bottomRight"
|
||||
title="Settings"
|
||||
trigger="click"
|
||||
>
|
||||
<Button style={{ marginRight: 8 }}>
|
||||
<SettingOutlined />
|
||||
Settings
|
||||
</Button>
|
||||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Select onSelect={setProvider} value={providerUrl}>
|
||||
{WALLET_PROVIDERS.map(({ name, url }) => (
|
||||
<Select.Option value={url} key={url}>
|
||||
{name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
type="text"
|
||||
size="large"
|
||||
onClick={connected ? wallet.disconnect : wallet.connect}
|
||||
style={{ color: '#2abdd2' }}
|
||||
>
|
||||
<UserOutlined />
|
||||
{!connected ? 'Connect wallet' : 'Disconnect'}
|
||||
</Button>
|
||||
{connected && (
|
||||
<Popover
|
||||
content={<LinkAddress address={publicKey} />}
|
||||
placement="bottomRight"
|
||||
title="Wallet public key"
|
||||
trigger="click"
|
||||
>
|
||||
<InfoCircleOutlined style={{ color: '#2abdd2' }} />
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
</Wrapper>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,18 +1,19 @@
|
|||
import { useLocalStorageState } from './utils';
|
||||
import { Account, AccountInfo, clusterApiUrl, Connection, PublicKey } from '@solana/web3.js';
|
||||
import React, { useContext, useEffect, useMemo } from 'react';
|
||||
import { setCache, useAsyncData } from './fetch-loop';
|
||||
import {useLocalStorageState} from './utils';
|
||||
import {Account, AccountInfo, clusterApiUrl, Connection, PublicKey} from '@solana/web3.js';
|
||||
import React, {useContext, useEffect, useMemo} from 'react';
|
||||
import {setCache, useAsyncData} from './fetch-loop';
|
||||
import tuple from 'immutable-tuple';
|
||||
import {ConnectionContextValues} from "./types";
|
||||
import {ConnectionContextValues, EndpointInfo} from "./types";
|
||||
|
||||
export const ENDPOINTS: {name: string; endpoint: string;}[] = [
|
||||
export const ENDPOINTS: EndpointInfo[] = [
|
||||
{
|
||||
name: 'mainnet-beta',
|
||||
endpoint: 'https://solana-api.projectserum.com',
|
||||
custom: false
|
||||
},
|
||||
{ name: 'testnet', endpoint: clusterApiUrl('testnet') },
|
||||
{ name: 'devnet', endpoint: clusterApiUrl('devnet') },
|
||||
{ name: 'localnet', endpoint: 'http://127.0.0.1:8899' },
|
||||
{ name: 'testnet', endpoint: clusterApiUrl('testnet'), custom: false },
|
||||
{ name: 'devnet', endpoint: clusterApiUrl('devnet'), custom: false },
|
||||
{ name: 'localnet', endpoint: 'http://127.0.0.1:8899', custom: false },
|
||||
];
|
||||
|
||||
const accountListenerCount = new Map();
|
||||
|
@ -24,6 +25,11 @@ export function ConnectionProvider({ children }) {
|
|||
'connectionEndpts',
|
||||
ENDPOINTS[0].endpoint,
|
||||
);
|
||||
const [customEndpoints, setCustomEndpoints] = useLocalStorageState<EndpointInfo[]>(
|
||||
'customConnectionEndpoints',
|
||||
[]
|
||||
)
|
||||
const availableEndpoints = ENDPOINTS.concat(customEndpoints);
|
||||
|
||||
const connection = useMemo(() => new Connection(endpoint, 'recent'), [
|
||||
endpoint,
|
||||
|
@ -60,7 +66,7 @@ export function ConnectionProvider({ children }) {
|
|||
|
||||
return (
|
||||
<ConnectionContext.Provider
|
||||
value={{ endpoint, setEndpoint, connection, sendConnection }}
|
||||
value={{ endpoint, setEndpoint, connection, sendConnection, availableEndpoints, setCustomEndpoints }}
|
||||
>
|
||||
{children}
|
||||
</ConnectionContext.Provider>
|
||||
|
@ -88,7 +94,12 @@ export function useConnectionConfig() {
|
|||
if (!context) {
|
||||
throw new Error('Missing connection context')
|
||||
}
|
||||
return { endpoint: context.endpoint, setEndpoint: context.setEndpoint };
|
||||
return {
|
||||
endpoint: context.endpoint,
|
||||
setEndpoint: context.setEndpoint,
|
||||
availableEndpoints: context.availableEndpoints,
|
||||
setCustomEndpoints: context.setCustomEndpoints,
|
||||
};
|
||||
}
|
||||
|
||||
export function useAccountInfo(publicKey: PublicKey | undefined | null): [AccountInfo<Buffer> | null | undefined, boolean] {
|
||||
|
|
|
@ -9,6 +9,8 @@ export interface ConnectionContextValues {
|
|||
setEndpoint: (newEndpoint: string) => void;
|
||||
connection: Connection;
|
||||
sendConnection: Connection;
|
||||
availableEndpoints: EndpointInfo[];
|
||||
setCustomEndpoints: (newCustomEndpoints: EndpointInfo[]) => void;
|
||||
}
|
||||
|
||||
export interface WalletContextValues {
|
||||
|
@ -106,3 +108,9 @@ export interface PreferencesContextValues {
|
|||
autoSettleEnabled: boolean;
|
||||
setAutoSettleEnabled: (newAutoSettleEnabled: boolean) => void;
|
||||
}
|
||||
|
||||
export interface EndpointInfo {
|
||||
name: string;
|
||||
endpoint: string;
|
||||
custom: boolean;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue