add hot key templates
This commit is contained in:
parent
616cc2cf66
commit
a2127fcfe2
|
@ -6,7 +6,9 @@ import { HotKey } from '@components/settings/HotKeysSettings'
|
|||
import Button from '@components/shared/Button'
|
||||
import InlineNotification from '@components/shared/InlineNotification'
|
||||
import Modal from '@components/shared/Modal'
|
||||
import TabUnderline from '@components/shared/TabUnderline'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { CheckIcon } from '@heroicons/react/20/solid'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
@ -16,6 +18,8 @@ import { HOT_KEYS_KEY } from 'utils/constants'
|
|||
type FormErrors = Partial<Record<keyof HotKeyForm, string>>
|
||||
|
||||
type HotKeyForm = {
|
||||
custom?: string
|
||||
name?: string
|
||||
baseKey: string
|
||||
triggerKey: string
|
||||
price: string
|
||||
|
@ -29,7 +33,34 @@ type HotKeyForm = {
|
|||
reduce: boolean
|
||||
}
|
||||
|
||||
type TEMPLATE = {
|
||||
price: string
|
||||
side: 'buy' | 'sell'
|
||||
size: string
|
||||
sizeType: 'percentage' | 'notional'
|
||||
orderType: 'limit' | 'market'
|
||||
ioc: boolean
|
||||
post: boolean
|
||||
margin: boolean
|
||||
reduce: boolean
|
||||
}
|
||||
|
||||
type CUSTOM_TEMPLATE = {
|
||||
custom: string
|
||||
}
|
||||
|
||||
export enum HOTKEY_TEMPLATES {
|
||||
CLOSE_LONG = 'Market close long position',
|
||||
CLOSE_SHORT = 'Market close short position',
|
||||
CLOSE_ALL_PERP = 'Market close all perp positions',
|
||||
}
|
||||
|
||||
const TEMPLATE_BUTTON_CLASSES =
|
||||
'flex w-full items-center justify-between border-t border-th-bkg-3 p-4 focus:outline-none md:hover:bg-th-bkg-2'
|
||||
|
||||
const DEFAULT_FORM_VALUES: HotKeyForm = {
|
||||
custom: '',
|
||||
name: '',
|
||||
baseKey: 'shift',
|
||||
triggerKey: '',
|
||||
price: '',
|
||||
|
@ -43,19 +74,69 @@ const DEFAULT_FORM_VALUES: HotKeyForm = {
|
|||
reduce: false,
|
||||
}
|
||||
|
||||
const CLOSE_LONG: TEMPLATE = {
|
||||
price: '',
|
||||
side: 'sell',
|
||||
size: '100',
|
||||
sizeType: 'percentage',
|
||||
orderType: 'market',
|
||||
ioc: false,
|
||||
post: false,
|
||||
margin: true,
|
||||
reduce: true,
|
||||
}
|
||||
|
||||
const CLOSE_SHORT: TEMPLATE = {
|
||||
price: '',
|
||||
side: 'buy',
|
||||
size: '100',
|
||||
sizeType: 'percentage',
|
||||
orderType: 'market',
|
||||
ioc: false,
|
||||
post: false,
|
||||
margin: true,
|
||||
reduce: true,
|
||||
}
|
||||
|
||||
const CLOSE_ALL_PERP: CUSTOM_TEMPLATE = {
|
||||
custom: HOTKEY_TEMPLATES.CLOSE_ALL_PERP,
|
||||
}
|
||||
|
||||
const TABS = ['settings:templates', 'settings:custom']
|
||||
|
||||
const HotKeyModal = ({ isOpen, onClose }: ModalProps) => {
|
||||
const { t } = useTranslation(['common', 'settings', 'trade'])
|
||||
const [activeTab, setActiveTab] = useState('settings:templates')
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<
|
||||
HOTKEY_TEMPLATES | ''
|
||||
>('')
|
||||
const [hotKeys, setHotKeys] = useLocalStorageState<HotKey[]>(HOT_KEYS_KEY, [])
|
||||
const [hotKeyForm, setHotKeyForm] = useState<HotKeyForm>({
|
||||
...DEFAULT_FORM_VALUES,
|
||||
})
|
||||
const [formErrors, setFormErrors] = useState<FormErrors>({})
|
||||
|
||||
const handleSwitchTab = (tab: string) => {
|
||||
setActiveTab(tab)
|
||||
setHotKeyForm({ ...DEFAULT_FORM_VALUES })
|
||||
setFormErrors({})
|
||||
setSelectedTemplate('')
|
||||
}
|
||||
|
||||
const handleSetForm = (propertyName: string, value: string | boolean) => {
|
||||
setFormErrors({})
|
||||
setHotKeyForm((prevState) => ({ ...prevState, [propertyName]: value }))
|
||||
}
|
||||
|
||||
const handleSetTemplate = (
|
||||
template: TEMPLATE | CUSTOM_TEMPLATE,
|
||||
templateName: HOTKEY_TEMPLATES,
|
||||
) => {
|
||||
setFormErrors({})
|
||||
setHotKeyForm((prevState) => ({ ...prevState, ...template }))
|
||||
setSelectedTemplate(templateName)
|
||||
}
|
||||
|
||||
const handlePostOnlyChange = (postOnly: boolean) => {
|
||||
if (postOnly) {
|
||||
handleSetForm('ioc', !postOnly)
|
||||
|
@ -131,27 +212,40 @@ const HotKeyModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
|
||||
const handleSave = () => {
|
||||
const invalidFields = isFormValid(hotKeyForm)
|
||||
if (Object.keys(invalidFields).length) {
|
||||
if (Object.keys(invalidFields).length && !hotKeyForm.custom) {
|
||||
return
|
||||
}
|
||||
const newHotKey = {
|
||||
keySequence: `${hotKeyForm.baseKey}+${hotKeyForm.triggerKey}`,
|
||||
orderSide: hotKeyForm.side,
|
||||
orderSizeType: hotKeyForm.sizeType,
|
||||
orderSize: hotKeyForm.size,
|
||||
orderType: hotKeyForm.orderType,
|
||||
orderPrice: hotKeyForm.price,
|
||||
ioc: hotKeyForm.ioc,
|
||||
margin: hotKeyForm.margin,
|
||||
postOnly: hotKeyForm.post,
|
||||
reduceOnly: hotKeyForm.reduce,
|
||||
}
|
||||
const name = hotKeyForm.name ? hotKeyForm.name : selectedTemplate || ''
|
||||
const newHotKey = !hotKeyForm.custom
|
||||
? {
|
||||
keySequence: `${hotKeyForm.baseKey}+${hotKeyForm.triggerKey}`,
|
||||
custom: hotKeyForm.custom,
|
||||
name: name,
|
||||
orderSide: hotKeyForm.side,
|
||||
orderSizeType: hotKeyForm.sizeType,
|
||||
orderSize: hotKeyForm.size,
|
||||
orderType: hotKeyForm.orderType,
|
||||
orderPrice: hotKeyForm.price,
|
||||
ioc: hotKeyForm.ioc,
|
||||
margin: hotKeyForm.margin,
|
||||
postOnly: hotKeyForm.post,
|
||||
reduceOnly: hotKeyForm.reduce,
|
||||
}
|
||||
: {
|
||||
keySequence: `${hotKeyForm.baseKey}+${hotKeyForm.triggerKey}`,
|
||||
custom: hotKeyForm.custom,
|
||||
name: name,
|
||||
}
|
||||
setHotKeys([...hotKeys, newHotKey])
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
panelClassNames="md:max-h-[calc(100vh-10%)] overflow-y-auto thin-scroll"
|
||||
>
|
||||
<>
|
||||
<h2 className="mb-4 text-center">{t('settings:new-hot-key')}</h2>
|
||||
<div className="mb-4">
|
||||
|
@ -184,155 +278,223 @@ const HotKeyModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
) : null}
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<Label text={t('settings:order-side')} />
|
||||
<ButtonGroup
|
||||
activeValue={hotKeyForm.side}
|
||||
names={[t('buy'), t('sell')]}
|
||||
onChange={(side) => handleSetForm('side', side)}
|
||||
values={['buy', 'sell']}
|
||||
<Label text={t('settings:nickname')} optional />
|
||||
<Input
|
||||
type="text"
|
||||
value={hotKeyForm.name || ''}
|
||||
onChange={(e) =>
|
||||
handleSetForm('name', e.target.value.toLowerCase())
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<Label text={t('trade:order-type')} />
|
||||
<ButtonGroup
|
||||
activeValue={hotKeyForm.orderType}
|
||||
names={[t('trade:limit'), t('market')]}
|
||||
onChange={(type) => handleSetForm('orderType', type)}
|
||||
values={['limit', 'market']}
|
||||
<div className="pb-2">
|
||||
<TabUnderline
|
||||
activeValue={activeTab}
|
||||
values={TABS}
|
||||
onChange={(v) => handleSwitchTab(v)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<Label text={t('settings:order-size-type')} />
|
||||
<ButtonGroup
|
||||
activeValue={hotKeyForm.sizeType}
|
||||
names={[t('settings:percentage'), t('settings:notional')]}
|
||||
onChange={(type) => handleSetForm('sizeType', type)}
|
||||
values={['percentage', 'notional']}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-start space-x-4">
|
||||
<div className="w-full">
|
||||
<Tooltip
|
||||
content={
|
||||
hotKeyForm.sizeType === 'notional'
|
||||
? t('settings:tooltip-hot-key-notional-size')
|
||||
: t('settings:tooltip-hot-key-percentage-size')
|
||||
{activeTab === 'settings:templates' ? (
|
||||
<div className="border-b border-th-bkg-3">
|
||||
<button
|
||||
className={TEMPLATE_BUTTON_CLASSES}
|
||||
onClick={() =>
|
||||
handleSetTemplate(CLOSE_LONG, HOTKEY_TEMPLATES.CLOSE_LONG)
|
||||
}
|
||||
>
|
||||
<Label className="tooltip-underline" text={t('trade:size')} />
|
||||
</Tooltip>
|
||||
<Input
|
||||
hasError={formErrors.size !== undefined}
|
||||
type="text"
|
||||
value={hotKeyForm.size}
|
||||
onChange={(e) => handleSetForm('size', e.target.value)}
|
||||
suffix={hotKeyForm.sizeType === 'percentage' ? '%' : 'USD'}
|
||||
/>
|
||||
{formErrors.size ? (
|
||||
<div className="mt-1">
|
||||
<InlineNotification
|
||||
type="error"
|
||||
desc={formErrors.size}
|
||||
hideBorder
|
||||
hidePadding
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{hotKeyForm.orderType === 'limit' ? (
|
||||
<div className="w-full">
|
||||
<Tooltip content={t('settings:tooltip-hot-key-price')}>
|
||||
<Label className="tooltip-underline" text={t('price')} />
|
||||
</Tooltip>
|
||||
<Input
|
||||
hasError={formErrors.price !== undefined}
|
||||
type="text"
|
||||
value={hotKeyForm.price}
|
||||
onChange={(e) => handleSetForm('price', e.target.value)}
|
||||
placeholder="e.g. 1%"
|
||||
suffix="%"
|
||||
<span className="text-th-fgd-2">
|
||||
{HOTKEY_TEMPLATES.CLOSE_LONG}
|
||||
</span>
|
||||
<TemplateCheckMark
|
||||
isActive={selectedTemplate === HOTKEY_TEMPLATES.CLOSE_LONG}
|
||||
/>
|
||||
{formErrors.price ? (
|
||||
<div className="mt-1">
|
||||
<InlineNotification
|
||||
type="error"
|
||||
desc={formErrors.price}
|
||||
hideBorder
|
||||
hidePadding
|
||||
</button>
|
||||
<button
|
||||
className={TEMPLATE_BUTTON_CLASSES}
|
||||
onClick={() =>
|
||||
handleSetTemplate(CLOSE_SHORT, HOTKEY_TEMPLATES.CLOSE_SHORT)
|
||||
}
|
||||
>
|
||||
<span className="text-th-fgd-2">
|
||||
{HOTKEY_TEMPLATES.CLOSE_SHORT}
|
||||
</span>
|
||||
<TemplateCheckMark
|
||||
isActive={selectedTemplate === HOTKEY_TEMPLATES.CLOSE_SHORT}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className={TEMPLATE_BUTTON_CLASSES}
|
||||
onClick={() =>
|
||||
handleSetTemplate(
|
||||
CLOSE_ALL_PERP,
|
||||
HOTKEY_TEMPLATES.CLOSE_ALL_PERP,
|
||||
)
|
||||
}
|
||||
>
|
||||
<span className="text-th-fgd-2">
|
||||
{HOTKEY_TEMPLATES.CLOSE_ALL_PERP}
|
||||
</span>
|
||||
<TemplateCheckMark
|
||||
isActive={selectedTemplate === HOTKEY_TEMPLATES.CLOSE_ALL_PERP}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<Label text={t('settings:order-side')} />
|
||||
<ButtonGroup
|
||||
activeValue={hotKeyForm.side}
|
||||
names={[t('buy'), t('sell')]}
|
||||
onChange={(side) => handleSetForm('side', side)}
|
||||
values={['buy', 'sell']}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<Label text={t('trade:order-type')} />
|
||||
<ButtonGroup
|
||||
activeValue={hotKeyForm.orderType}
|
||||
names={[t('trade:limit'), t('market')]}
|
||||
onChange={(type) => handleSetForm('orderType', type)}
|
||||
values={['limit', 'market']}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<Label text={t('settings:order-size-type')} />
|
||||
<ButtonGroup
|
||||
activeValue={hotKeyForm.sizeType}
|
||||
names={[t('settings:percentage'), t('settings:notional')]}
|
||||
onChange={(type) => handleSetForm('sizeType', type)}
|
||||
values={['percentage', 'notional']}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-start space-x-4">
|
||||
<div className="w-full">
|
||||
<Tooltip
|
||||
content={
|
||||
hotKeyForm.sizeType === 'notional'
|
||||
? t('settings:tooltip-hot-key-notional-size')
|
||||
: t('settings:tooltip-hot-key-percentage-size')
|
||||
}
|
||||
>
|
||||
<Label className="tooltip-underline" text={t('trade:size')} />
|
||||
</Tooltip>
|
||||
<Input
|
||||
hasError={formErrors.size !== undefined}
|
||||
type="text"
|
||||
value={hotKeyForm.size}
|
||||
onChange={(e) => handleSetForm('size', e.target.value)}
|
||||
suffix={hotKeyForm.sizeType === 'percentage' ? '%' : 'USD'}
|
||||
/>
|
||||
{formErrors.size ? (
|
||||
<div className="mt-1">
|
||||
<InlineNotification
|
||||
type="error"
|
||||
desc={formErrors.size}
|
||||
hideBorder
|
||||
hidePadding
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{hotKeyForm.orderType === 'limit' ? (
|
||||
<div className="w-full">
|
||||
<Tooltip content={t('settings:tooltip-hot-key-price')}>
|
||||
<Label className="tooltip-underline" text={t('price')} />
|
||||
</Tooltip>
|
||||
<Input
|
||||
hasError={formErrors.price !== undefined}
|
||||
type="text"
|
||||
value={hotKeyForm.price}
|
||||
onChange={(e) => handleSetForm('price', e.target.value)}
|
||||
placeholder="e.g. 1%"
|
||||
suffix="%"
|
||||
/>
|
||||
{formErrors.price ? (
|
||||
<div className="mt-1">
|
||||
<InlineNotification
|
||||
type="error"
|
||||
desc={formErrors.price}
|
||||
hideBorder
|
||||
hidePadding
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex flex-wrap md:flex-nowrap">
|
||||
{hotKeyForm.orderType === 'limit' ? (
|
||||
<div className="flex">
|
||||
<div className="mr-3 mt-4" id="trade-step-six">
|
||||
<div className="flex flex-wrap md:flex-nowrap">
|
||||
{hotKeyForm.orderType === 'limit' ? (
|
||||
<div className="flex">
|
||||
<div className="mr-3 mt-4" id="trade-step-six">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={100}
|
||||
content={t('trade:tooltip-post')}
|
||||
>
|
||||
<Checkbox
|
||||
checked={hotKeyForm.post}
|
||||
onChange={(e) => handlePostOnlyChange(e.target.checked)}
|
||||
>
|
||||
{t('trade:post')}
|
||||
</Checkbox>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="mr-3 mt-4" id="trade-step-seven">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={100}
|
||||
content={t('trade:tooltip-ioc')}
|
||||
>
|
||||
<div className="flex items-center text-xs text-th-fgd-3">
|
||||
<Checkbox
|
||||
checked={hotKeyForm.ioc}
|
||||
onChange={(e) => handleIocChange(e.target.checked)}
|
||||
>
|
||||
IOC
|
||||
</Checkbox>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="mr-3 mt-4" id="trade-step-eight">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={100}
|
||||
content={t('trade:tooltip-post')}
|
||||
content={t('trade:tooltip-enable-margin')}
|
||||
>
|
||||
<Checkbox
|
||||
checked={hotKeyForm.post}
|
||||
onChange={(e) => handlePostOnlyChange(e.target.checked)}
|
||||
checked={hotKeyForm.margin}
|
||||
onChange={(e) => handleSetForm('margin', e.target.checked)}
|
||||
>
|
||||
{t('trade:post')}
|
||||
{t('trade:margin')}
|
||||
</Checkbox>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="mr-3 mt-4" id="trade-step-seven">
|
||||
<div className="mr-3 mt-4">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={100}
|
||||
content={t('trade:tooltip-ioc')}
|
||||
content={
|
||||
'Reduce will only decrease the size of an open position. This is often used for closing a position.'
|
||||
}
|
||||
>
|
||||
<div className="flex items-center text-xs text-th-fgd-3">
|
||||
<Checkbox
|
||||
checked={hotKeyForm.ioc}
|
||||
onChange={(e) => handleIocChange(e.target.checked)}
|
||||
checked={hotKeyForm.reduce}
|
||||
onChange={(e) =>
|
||||
handleSetForm('reduce', e.target.checked)
|
||||
}
|
||||
>
|
||||
IOC
|
||||
{t('trade:reduce-only')}
|
||||
</Checkbox>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="mr-3 mt-4" id="trade-step-eight">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={100}
|
||||
content={t('trade:tooltip-enable-margin')}
|
||||
>
|
||||
<Checkbox
|
||||
checked={hotKeyForm.margin}
|
||||
onChange={(e) => handleSetForm('margin', e.target.checked)}
|
||||
>
|
||||
{t('trade:margin')}
|
||||
</Checkbox>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="mr-3 mt-4">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={100}
|
||||
content={
|
||||
'Reduce will only decrease the size of an open position. This is often used for closing a position.'
|
||||
}
|
||||
>
|
||||
<div className="flex items-center text-xs text-th-fgd-3">
|
||||
<Checkbox
|
||||
checked={hotKeyForm.reduce}
|
||||
onChange={(e) => handleSetForm('reduce', e.target.checked)}
|
||||
>
|
||||
{t('trade:reduce-only')}
|
||||
</Checkbox>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<Button className="mt-6 w-full" onClick={handleSave}>
|
||||
{t('settings:save-hot-key')}
|
||||
</Button>
|
||||
|
@ -342,3 +504,13 @@ const HotKeyModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
}
|
||||
|
||||
export default HotKeyModal
|
||||
|
||||
const TemplateCheckMark = ({ isActive }: { isActive: boolean }) => {
|
||||
return isActive ? (
|
||||
<div className="flex h-5 w-5 items-center justify-center rounded-full bg-th-success">
|
||||
<CheckIcon className="h-4 w-4 text-th-bkg-1" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-5 w-5 rounded-full bg-th-bkg-4" />
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { ModalProps } from '../../types/modal'
|
||||
import Modal from '../shared/Modal'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
@ -41,6 +41,12 @@ const SettingsModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
isDesktop ? TABS[0] : null,
|
||||
)
|
||||
|
||||
const tabsToShow = useMemo(() => {
|
||||
if (isDesktop) {
|
||||
return TABS
|
||||
} else return TABS.slice(0, -1)
|
||||
}, [isDesktop])
|
||||
|
||||
// set an active tab is screen width is desktop and no tab is set
|
||||
useEffect(() => {
|
||||
if (!activeTab && isDesktop) {
|
||||
|
@ -59,8 +65,8 @@ const SettingsModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
<h2 className="mb-6">{t('settings')}</h2>
|
||||
<div className="grid grid-cols-12 md:gap-8">
|
||||
{isDesktop || !activeTab ? (
|
||||
<div className="col-span-12 space-y-2 md:col-span-3 lg:col-span-4">
|
||||
{TABS.map((tab) => (
|
||||
<div className="col-span-12 space-y-2 md:col-span-3 2xl:col-span-4">
|
||||
{tabsToShow.map((tab) => (
|
||||
<TabButton
|
||||
activeTab={activeTab}
|
||||
key={tab}
|
||||
|
@ -71,7 +77,7 @@ const SettingsModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
</div>
|
||||
) : null}
|
||||
{isDesktop || activeTab ? (
|
||||
<div className="col-span-12 md:col-span-9 lg:col-span-8">
|
||||
<div className="col-span-12 md:col-span-9 2xl:col-span-8">
|
||||
<TabContent activeTab={activeTab} setActiveTab={setActiveTab} />
|
||||
</div>
|
||||
) : null}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import KeyboardIcon from '@components/icons/KeyboardIcon'
|
||||
import HotKeyModal from '@components/modals/HotKeyModal'
|
||||
import HotKeyModal, { HOTKEY_TEMPLATES } from '@components/modals/HotKeyModal'
|
||||
import Button, { IconButton } from '@components/shared/Button'
|
||||
import InlineNotification from '@components/shared/InlineNotification'
|
||||
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
||||
|
@ -10,6 +10,8 @@ import { useState } from 'react'
|
|||
import { HOT_KEYS_KEY } from 'utils/constants'
|
||||
|
||||
export type HotKey = {
|
||||
custom?: HOTKEY_TEMPLATES
|
||||
name?: string
|
||||
ioc: boolean
|
||||
keySequence: string
|
||||
margin: boolean
|
||||
|
@ -61,6 +63,7 @@ const HotKeysSettings = () => {
|
|||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">{t('settings:nickname')}</Th>
|
||||
<Th className="text-left">{t('settings:key-sequence')}</Th>
|
||||
<Th className="text-right">{t('trade:order-type')}</Th>
|
||||
<Th className="text-right">{t('trade:side')}</Th>
|
||||
|
@ -73,6 +76,7 @@ const HotKeysSettings = () => {
|
|||
<tbody>
|
||||
{hotKeys.map((hk: HotKey) => {
|
||||
const {
|
||||
name,
|
||||
keySequence,
|
||||
orderSide,
|
||||
orderPrice,
|
||||
|
@ -84,10 +88,11 @@ const HotKeysSettings = () => {
|
|||
reduceOnly,
|
||||
postOnly,
|
||||
} = hk
|
||||
const size =
|
||||
orderSizeType === 'percentage'
|
||||
const size = orderSize
|
||||
? orderSizeType === 'percentage'
|
||||
? t('settings:percentage-of-max', { size: orderSize })
|
||||
: `$${orderSize}`
|
||||
: '–'
|
||||
const price = orderPrice
|
||||
? `${orderPrice}% ${
|
||||
orderSide === 'buy'
|
||||
|
@ -105,9 +110,14 @@ const HotKeysSettings = () => {
|
|||
|
||||
return (
|
||||
<TrBody key={keySequence} className="text-right text-th-fgd-2">
|
||||
<Td className="text-left">{keySequence}</Td>
|
||||
<Td className="text-right">{t(`trade:${orderType}`)}</Td>
|
||||
<Td className="text-right">{t(orderSide)}</Td>
|
||||
<Td className="text-left">{name || '–'}</Td>
|
||||
<Td className="text-right">{keySequence}</Td>
|
||||
<Td className="text-right">
|
||||
{orderType ? t(`trade:${orderType}`) : '–'}
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
{orderSide ? t(orderSide) : '–'}
|
||||
</Td>
|
||||
<Td className="text-right">{size}</Td>
|
||||
<Td className="text-right">{price}</Td>
|
||||
<Td className="text-right">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { FunctionComponent, useCallback, useState } from 'react'
|
||||
import { FunctionComponent, useState } from 'react'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Modal from '@components/shared/Modal'
|
||||
|
@ -14,6 +14,58 @@ import MarketLogos from './MarketLogos'
|
|||
import PerpSideBadge from './PerpSideBadge'
|
||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
|
||||
export const handleCloseAll = async (
|
||||
setSubmitting?: (s: boolean) => void,
|
||||
onClose?: () => void,
|
||||
) => {
|
||||
const client = mangoStore.getState().client
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const actions = mangoStore.getState().actions
|
||||
const group = mangoStore.getState().group
|
||||
|
||||
if (!group || !mangoAccount) {
|
||||
notify({
|
||||
title: 'Something went wrong. Try again later',
|
||||
type: 'error',
|
||||
})
|
||||
return
|
||||
}
|
||||
if (setSubmitting) {
|
||||
setSubmitting(true)
|
||||
}
|
||||
try {
|
||||
const maxSlippage = 0.025
|
||||
const { signature: tx } = await client.perpCloseAll(
|
||||
group,
|
||||
mangoAccount,
|
||||
maxSlippage,
|
||||
)
|
||||
actions.fetchOpenOrders()
|
||||
notify({
|
||||
type: 'success',
|
||||
title: 'Transaction successful',
|
||||
txid: tx,
|
||||
})
|
||||
} catch (e) {
|
||||
if (isMangoError(e)) {
|
||||
notify({
|
||||
title: 'There was an issue.',
|
||||
description: e.message,
|
||||
txid: e?.txid,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
console.error('Place trade error:', e)
|
||||
} finally {
|
||||
if (setSubmitting) {
|
||||
setSubmitting(false)
|
||||
}
|
||||
if (onClose) {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CloseAllPositionsModal: FunctionComponent<ModalProps> = ({
|
||||
onClose,
|
||||
isOpen,
|
||||
|
@ -23,47 +75,47 @@ const CloseAllPositionsModal: FunctionComponent<ModalProps> = ({
|
|||
const openPerpPositions = useOpenPerpPositions()
|
||||
const { group } = useMangoGroup()
|
||||
|
||||
const handleCloseAll = useCallback(async () => {
|
||||
const client = mangoStore.getState().client
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const actions = mangoStore.getState().actions
|
||||
// const handleCloseAll = useCallback(async () => {
|
||||
// const client = mangoStore.getState().client
|
||||
// const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
// const actions = mangoStore.getState().actions
|
||||
|
||||
if (!group || !mangoAccount) {
|
||||
notify({
|
||||
title: 'Something went wrong. Try again later',
|
||||
type: 'error',
|
||||
})
|
||||
return
|
||||
}
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const maxSlippage = 0.025
|
||||
const { signature: tx } = await client.perpCloseAll(
|
||||
group,
|
||||
mangoAccount,
|
||||
maxSlippage,
|
||||
)
|
||||
actions.fetchOpenOrders()
|
||||
notify({
|
||||
type: 'success',
|
||||
title: 'Transaction successful',
|
||||
txid: tx,
|
||||
})
|
||||
} catch (e) {
|
||||
if (isMangoError(e)) {
|
||||
notify({
|
||||
title: 'There was an issue.',
|
||||
description: e.message,
|
||||
txid: e?.txid,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
console.error('Place trade error:', e)
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
onClose()
|
||||
}
|
||||
}, [group, onClose])
|
||||
// if (!group || !mangoAccount) {
|
||||
// notify({
|
||||
// title: 'Something went wrong. Try again later',
|
||||
// type: 'error',
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
// setSubmitting(true)
|
||||
// try {
|
||||
// const maxSlippage = 0.025
|
||||
// const { signature: tx } = await client.perpCloseAll(
|
||||
// group,
|
||||
// mangoAccount,
|
||||
// maxSlippage,
|
||||
// )
|
||||
// actions.fetchOpenOrders()
|
||||
// notify({
|
||||
// type: 'success',
|
||||
// title: 'Transaction successful',
|
||||
// txid: tx,
|
||||
// })
|
||||
// } catch (e) {
|
||||
// if (isMangoError(e)) {
|
||||
// notify({
|
||||
// title: 'There was an issue.',
|
||||
// description: e.message,
|
||||
// txid: e?.txid,
|
||||
// type: 'error',
|
||||
// })
|
||||
// }
|
||||
// console.error('Place trade error:', e)
|
||||
// } finally {
|
||||
// setSubmitting(false)
|
||||
// onClose()
|
||||
// }
|
||||
// }, [group, onClose])
|
||||
|
||||
if (!group) return null
|
||||
|
||||
|
@ -106,7 +158,7 @@ const CloseAllPositionsModal: FunctionComponent<ModalProps> = ({
|
|||
</div>
|
||||
<Button
|
||||
className="mb-4 mt-6 flex w-full items-center justify-center"
|
||||
onClick={handleCloseAll}
|
||||
onClick={() => handleCloseAll(setSubmitting, onClose)}
|
||||
size="large"
|
||||
>
|
||||
{submitting ? (
|
||||
|
|
|
@ -96,7 +96,9 @@ const HotKeysDrawer = ({
|
|||
<div className="border-b border-th-bkg-3">
|
||||
{hotKeys.map((hk: HotKey) => {
|
||||
const {
|
||||
custom,
|
||||
keySequence,
|
||||
name,
|
||||
orderSide,
|
||||
orderPrice,
|
||||
orderSize,
|
||||
|
@ -135,27 +137,32 @@ const HotKeysDrawer = ({
|
|||
key={keySequence}
|
||||
>
|
||||
<div>
|
||||
<p className="font-bold text-th-fgd-2">
|
||||
<p className="text-th-fgd-1">{name}</p>
|
||||
<p className="font-bold text-th-active">
|
||||
{keySequence}
|
||||
</p>
|
||||
<p>{`${t(orderSide)} ${t(
|
||||
`trade:${orderType}`,
|
||||
)}, ${size} at ${price}`}</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
{!options.margin &&
|
||||
selectedMarket instanceof PerpMarket ? (
|
||||
<div className={BADGE_CLASSNAMES}>
|
||||
{t('trade:margin')}
|
||||
</div>
|
||||
) : null}
|
||||
{Object.entries(options).map((e) => {
|
||||
return e[1] ? (
|
||||
{!custom ? (
|
||||
<p>{`${t(orderSide)} ${t(
|
||||
`trade:${orderType}`,
|
||||
)}, ${size} at ${price}`}</p>
|
||||
) : null}
|
||||
{!custom ? (
|
||||
<div className="flex items-center space-x-2">
|
||||
{!options.margin &&
|
||||
selectedMarket instanceof PerpMarket ? (
|
||||
<div className={BADGE_CLASSNAMES}>
|
||||
{t(`trade:${e[0]}`)}
|
||||
{t('trade:margin')}
|
||||
</div>
|
||||
) : null
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
{Object.entries(options).map((e) => {
|
||||
return e[1] ? (
|
||||
<div className={BADGE_CLASSNAMES}>
|
||||
{t(`trade:${e[0]}`)}
|
||||
</div>
|
||||
) : null
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="pl-4">
|
||||
<IconButton
|
||||
|
|
|
@ -24,6 +24,8 @@ import { floorToDecimal, getDecimalCount } from 'utils/numbers'
|
|||
import { Market } from '@project-serum/serum'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useCustomHotkeys } from 'hooks/useCustomHotKeys'
|
||||
import { HOTKEY_TEMPLATES } from '@components/modals/HotKeyModal'
|
||||
import { handleCloseAll } from './CloseAllPositionsModal'
|
||||
|
||||
const set = mangoStore.getState().set
|
||||
|
||||
|
@ -173,6 +175,14 @@ const TradeHotKeys = ({ children }: { children: ReactNode }) => {
|
|||
INITIAL_SOUND_SETTINGS,
|
||||
)
|
||||
|
||||
const handleHotKeyPress = (hkOrder: HotKey) => {
|
||||
if (hkOrder.custom === HOTKEY_TEMPLATES.CLOSE_ALL_PERP) {
|
||||
handleCloseAll()
|
||||
} else {
|
||||
handlePlaceOrder(hkOrder)
|
||||
}
|
||||
}
|
||||
|
||||
const handlePlaceOrder = useCallback(
|
||||
async (hkOrder: HotKey) => {
|
||||
const client = mangoStore.getState().client
|
||||
|
@ -251,7 +261,6 @@ const TradeHotKeys = ({ children }: { children: ReactNode }) => {
|
|||
return
|
||||
}
|
||||
} else {
|
||||
console.log(baseSize, orderMax)
|
||||
if (baseSize > orderMax) {
|
||||
notify({
|
||||
type: 'error',
|
||||
|
@ -353,7 +362,7 @@ const TradeHotKeys = ({ children }: { children: ReactNode }) => {
|
|||
[serumOrPerpMarket],
|
||||
)
|
||||
|
||||
useCustomHotkeys(handlePlaceOrder)
|
||||
useCustomHotkeys(handleHotKeyPress)
|
||||
|
||||
return <>{children}</>
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
"medium": "Medium",
|
||||
"network": "Network",
|
||||
"new-hot-key": "New Hot Key",
|
||||
"nickname": "Nickname",
|
||||
"no-hot-keys": "Create your first hot key",
|
||||
"notification-position": "Notification Position",
|
||||
"notifications": "Notifications",
|
||||
|
@ -107,6 +108,7 @@
|
|||
"swap-success": "Swap/Trade Success",
|
||||
"swap-trade-size-selector": "Swap/Trade Size Selector",
|
||||
"telemetry": "Telemetry",
|
||||
"templates": "Templates",
|
||||
"theme": "Theme",
|
||||
"tooltip-close-collateral-token-instructions": "Close spot open orders slots and cancel trigger orders with {{token}} in the pair. Make sure you have enough free collateral to withdraw your total {{token}} balance.",
|
||||
"tooltip-close-token-instructions": "Close spot open orders slots and cancel trigger orders with {{token}} in the pair.",
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
"medium": "Medium",
|
||||
"network": "Network",
|
||||
"new-hot-key": "New Hot Key",
|
||||
"nickname": "Nickname",
|
||||
"no-hot-keys": "Create your first hot key",
|
||||
"notification-position": "Notification Position",
|
||||
"notifications": "Notifications",
|
||||
|
@ -107,6 +108,7 @@
|
|||
"swap-success": "Swap/Trade Success",
|
||||
"swap-trade-size-selector": "Swap/Trade Size Selector",
|
||||
"telemetry": "Telemetry",
|
||||
"templates": "Templates",
|
||||
"theme": "Theme",
|
||||
"tooltip-close-collateral-token-instructions": "Close spot open orders slots and cancel trigger orders with {{token}} in the pair. Make sure you have enough free collateral to withdraw your total {{token}} balance.",
|
||||
"tooltip-close-token-instructions": "Close spot open orders slots and cancel trigger orders with {{token}} in the pair.",
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
"medium": "Medium",
|
||||
"network": "Network",
|
||||
"new-hot-key": "New Hot Key",
|
||||
"nickname": "Nickname",
|
||||
"no-hot-keys": "Create your first hot key",
|
||||
"notification-position": "Notification Position",
|
||||
"notifications": "Notifications",
|
||||
|
@ -107,6 +108,7 @@
|
|||
"swap-success": "Swap/Trade Success",
|
||||
"swap-trade-size-selector": "Swap/Trade Size Selector",
|
||||
"telemetry": "Telemetry",
|
||||
"templates": "Templates",
|
||||
"theme": "Theme",
|
||||
"tooltip-close-collateral-token-instructions": "Close spot open orders slots and cancel trigger orders with {{token}} in the pair. Make sure you have enough free collateral to withdraw your total {{token}} balance.",
|
||||
"tooltip-close-token-instructions": "Close spot open orders slots and cancel trigger orders with {{token}} in the pair.",
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
"medium": "中",
|
||||
"network": "Network",
|
||||
"new-hot-key": "新热键",
|
||||
"nickname": "Nickname",
|
||||
"no-hot-keys": "创建你的第一个热键",
|
||||
"notification-position": "通知位置",
|
||||
"notifications": "通知",
|
||||
|
@ -108,6 +109,7 @@
|
|||
"swap-success": "换币/交易成功",
|
||||
"swap-trade-size-selector": "换币/交易大小选择器",
|
||||
"telemetry": "Telemetry",
|
||||
"templates": "Templates",
|
||||
"theme": "模式",
|
||||
"tooltip-close-collateral-token-instructions": "Close spot open orders slots and cancel trigger orders with {{token}} in the pair. Make sure you have enough free collateral to withdraw your total {{token}} balance.",
|
||||
"tooltip-close-token-instructions": "Close spot open orders slots and cancel trigger orders with {{token}} in the pair.",
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
"medium": "中",
|
||||
"network": "Network",
|
||||
"new-hot-key": "新熱鍵",
|
||||
"nickname": "Nickname",
|
||||
"no-hot-keys": "創建你的第一個熱鍵",
|
||||
"notification-position": "通知位置",
|
||||
"notifications": "通知",
|
||||
|
@ -108,6 +109,7 @@
|
|||
"swap-success": "換幣/交易成功",
|
||||
"swap-trade-size-selector": "換幣/交易大小選擇器",
|
||||
"telemetry": "Telemetry",
|
||||
"templates": "Templates",
|
||||
"theme": "模式",
|
||||
"tooltip-close-collateral-token-instructions": "Close spot open orders slots and cancel trigger orders with {{token}} in the pair. Make sure you have enough free collateral to withdraw your total {{token}} balance.",
|
||||
"tooltip-close-token-instructions": "Close spot open orders slots and cancel trigger orders with {{token}} in the pair.",
|
||||
|
|
Loading…
Reference in New Issue