import ButtonGroup from '@components/forms/ButtonGroup' import Checkbox from '@components/forms/Checkbox' import Input from '@components/forms/Input' import Label from '@components/forms/Label' 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' import { ModalProps } from 'types/modal' import { HOT_KEYS_KEY } from 'utils/constants' type FormErrors = Partial> type HotKeyForm = { custom?: string name?: string baseKey: string triggerKey: string price: string side: 'buy' | 'sell' size: string sizeType: 'percentage' | 'notional' orderType: 'limit' | 'market' ioc: boolean post: boolean margin: boolean reduce: boolean } type TEMPLATE = { custom?: string 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 side?: 'buy' | 'sell' orderType?: 'limit' | 'market' reduce?: boolean } 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: '', side: 'buy', size: '', sizeType: 'percentage', orderType: 'limit', ioc: false, post: false, margin: false, reduce: false, } const CLOSE_LONG: CUSTOM_TEMPLATE = { custom: HOTKEY_TEMPLATES.CLOSE_LONG, side: 'sell', orderType: 'market', reduce: true, } const CLOSE_SHORT: CUSTOM_TEMPLATE = { custom: HOTKEY_TEMPLATES.CLOSE_SHORT, side: 'buy', orderType: 'market', 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(HOT_KEYS_KEY, []) const [hotKeyForm, setHotKeyForm] = useState({ ...DEFAULT_FORM_VALUES, }) const [formErrors, setFormErrors] = useState({}) const handleSwitchTab = (tab: string) => { setActiveTab(tab) setHotKeyForm({ ...DEFAULT_FORM_VALUES, name: hotKeyForm.name, triggerKey: hotKeyForm.triggerKey, }) 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) } handleSetForm('post', postOnly) } const handleIocChange = (ioc: boolean) => { if (ioc) { handleSetForm('post', !ioc) } handleSetForm('ioc', ioc) } const isFormValid = (form: HotKeyForm) => { const invalidFields: FormErrors = {} setFormErrors({}) const triggerKey: (keyof HotKeyForm)[] = ['triggerKey'] const requiredFields: (keyof HotKeyForm)[] = ['size', 'price', 'triggerKey'] const numberFields: (keyof HotKeyForm)[] = ['size', 'price'] const alphanumericRegex = /^[a-zA-Z0-9]+$/ for (const key of triggerKey) { const value = form[key] as string if (value.length > 1) { invalidFields[key] = t('settings:error-too-many-characters') } if (!alphanumericRegex.test(value)) { invalidFields[key] = t('settings:error-alphanumeric-only') } } for (const key of requiredFields) { const value = form[key] as string if (!value) { if (hotKeyForm.orderType === 'market') { if (key !== 'price') { invalidFields[key] = t('settings:error-required-field') } } else { invalidFields[key] = t('settings:error-required-field') } } } for (const key of numberFields) { const value = form[key] as string if (value) { if (isNaN(parseFloat(value))) { invalidFields[key] = t('settings:error-must-be-number') } if (parseFloat(value) < 0) { invalidFields[key] = t('settings:error-must-be-above-zero') } if (parseFloat(value) > 100) { if (key === 'price') { invalidFields[key] = t('settings:error-must-be-below-100') } else { if (hotKeyForm.sizeType === 'percentage') { invalidFields[key] = t('settings:error-must-be-below-100') } } } } } const newKeySequence = `${form.baseKey}+${form.triggerKey}` const keyExists = hotKeys.find((k) => k.keySequence === newKeySequence) if (keyExists) { invalidFields.triggerKey = t('settings:error-key-in-use') } if (Object.keys(invalidFields).length) { setFormErrors(invalidFields) } return invalidFields } const handleSave = () => { const invalidFields = isFormValid(hotKeyForm) if (Object.keys(invalidFields).length && !hotKeyForm.custom) { return } 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, orderSide: hotKeyForm.side, orderType: hotKeyForm.orderType, reduceOnly: hotKeyForm.reduce, } setHotKeys([...hotKeys, newHotKey]) onClose() } return ( <>

{t('settings:new-hot-key')}

handleSwitchTab(v)} />
{activeTab === 'settings:templates' ? (
) : ( <>
handleSetForm('size', e.target.value)} suffix={hotKeyForm.sizeType === 'percentage' ? '%' : 'USD'} /> {formErrors.size ? (
) : null}
{hotKeyForm.orderType === 'limit' ? (
handleSetForm('price', e.target.value)} placeholder="e.g. 1%" suffix="%" /> {formErrors.price ? (
) : null}
) : null}
{hotKeyForm.orderType === 'limit' ? (
handlePostOnlyChange(e.target.checked)} > {t('trade:post')}
handleIocChange(e.target.checked)} > IOC
) : null}
handleSetForm('margin', e.target.checked)} > {t('trade:margin')}
handleSetForm('reduce', e.target.checked) } > {t('trade:reduce-only')}
)}
) } export default HotKeyModal const TemplateCheckMark = ({ isActive }: { isActive: boolean }) => { return isActive ? (
) : (
) }