trigger set alerts modal from margininfo

This commit is contained in:
saml33 2021-05-02 00:02:21 +10:00
parent 9dc5b1479b
commit 23220e3555
17 changed files with 451 additions and 48 deletions

329
components/AlertsModal.tsx Normal file
View File

@ -0,0 +1,329 @@
import React, { useEffect, useState } from 'react'
import { RadioGroup } from '@headlessui/react'
import { InformationCircleIcon, DuplicateIcon } from '@heroicons/react/outline'
import PhoneInput from 'react-phone-input-2'
import 'react-phone-input-2/lib/plain.css'
import Button from './Button'
import Input from './Input'
import { ElementTitle } from './styles'
import useMangoStore from '../stores/useMangoStore'
import { notify } from '../utils/notifications'
import { copyToClipboard } from '../utils'
import Modal from './Modal'
import Loading from './Loading'
import MarginAccountSelect from './MarginAccountSelect'
import Tooltip from './Tooltip'
export default function AlertsModal({ isOpen, onClose }) {
const connected = useMangoStore((s) => s.wallet.connected)
const marginAccounts = useMangoStore((s) => s.marginAccounts)
const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const [selectedMarginAccount, setSelectedMarginAccount] = useState<any>(
marginAccounts[0]
)
const [collateralRatioThresh, setCollateralRatioThresh] = useState(113)
const [alertProvider, setAlertProvider] = useState('sms')
const [phoneNumber, setPhoneNumber] = useState<any>({ phone: null })
const [email, setEmail] = useState<string>('')
const [tgCode, setTgCode] = useState<string>('')
const [submitting, setSubmitting] = useState(false)
const [isCopied, setIsCopied] = useState(false)
useEffect(() => {
if (isCopied) {
const timer = setTimeout(() => {
setIsCopied(false)
}, 2000)
return () => clearTimeout(timer)
}
}, [isCopied])
const handleCopyTgCode = (code) => {
setIsCopied(true)
copyToClipboard(code)
}
const resetForm = () => {
setAlertProvider('sms')
setPhoneNumber({ phone: null })
setEmail('')
setTgCode('')
setCollateralRatioThresh(113)
}
async function onSubmit() {
if (!connected) {
notify({
message: 'Please connect wallet',
type: 'error',
})
return
} else if (!selectedMarginAccount) {
notify({
message: 'Please select a margin account',
type: 'error',
})
return
} else if (alertProvider === 'sms' && !phoneNumber.phone) {
notify({
message: 'Please provide phone number',
type: 'error',
})
return
} else if (alertProvider === 'mail' && !email) {
notify({
message: 'Please provide e-mail',
type: 'error',
})
return
}
setSubmitting(true)
const fetchUrl = `https://mango-margin-call.herokuapp.com/alerts`
const body = {
mangoGroupPk: selectedMangoGroup.publicKey.toString(),
marginAccountPk: selectedMarginAccount.publicKey.toString(),
collateralRatioThresh,
alertProvider,
phoneNumber,
email,
}
const headers = { 'Content-Type': 'application/json' }
fetch(fetchUrl, {
method: 'POST',
headers: headers,
body: JSON.stringify(body),
})
.then((response: any) => {
if (!response.ok) {
throw response
}
return response.json()
})
.then((json: any) => {
if (alertProvider === 'tg') {
setTgCode(json.code)
} else {
notify({
message: 'You have succesfully saved your alert',
type: 'success',
})
resetForm()
}
})
.catch((err) => {
if (typeof err.text === 'function') {
err.text().then((errorMessage: string) => {
notify({
message: errorMessage,
type: 'error',
})
})
} else {
notify({
message: 'Something went wrong',
type: 'error',
})
}
})
.finally(() => {
setSubmitting(false)
})
}
return (
<Modal isOpen={isOpen} onClose={onClose}>
{tgCode !== '' ? (
<TelegramModal
tgCode={tgCode}
setTgCode={setTgCode}
handleCopyToClipboard={handleCopyTgCode}
isCopied={isCopied}
/>
) : (
<>
<Modal.Header>
<div className={`text-th-fgd-3 flex-shrink invisible w-5`}>X</div>
<ElementTitle noMarignBottom>
Set Liquidation Alert{' '}
<Tooltip
content="Your account can be liquidated if your collateral ratio is below 110%.
Set an alert above 110% and we'll let you know if it falls
below that value."
>
<div>
<InformationCircleIcon
className={`h-5 w-5 ml-2 text-th-primary cursor-help`}
/>
</div>
</Tooltip>
</ElementTitle>
</Modal.Header>
<div className="pb-4">
<div className={`text-th-fgd-1 pb-2`}>Margin Account</div>
<MarginAccountSelect
value={marginAccounts[0]}
onChange={setSelectedMarginAccount}
/>
</div>
<div className="pb-4">
<div className={`text-th-fgd-1 pb-2`}>
Alert me when my collateral ratio is below:
</div>
<Input
type="number"
value={collateralRatioThresh}
onChange={(e) => setCollateralRatioThresh(e.target.value)}
suffix="%"
/>
</div>
<div className="pb-4">
<div className={`text-th-fgd-1 pb-2`}>Alert me via:</div>
<RadioGroup
value={alertProvider}
onChange={(val) => setAlertProvider(val)}
className="flex border border-th-fgd-4 rounded"
>
<RadioGroup.Option
value="sms"
className="flex-1 focus:outline-none"
>
{({ checked }) => (
<button
className={`${
checked ? 'bg-th-bkg-3 rounded-l' : ''
} font-normal text-th-fgd-1 text-center py-1.5 w-full rounded-none border-r border-th-fgd-4 hover:bg-th-bkg-3 focus:outline-none`}
>
SMS
</button>
)}
</RadioGroup.Option>
<RadioGroup.Option
value="mail"
className="focus:outline-none flex-1"
>
{({ checked }) => (
<button
className={`${
checked ? 'bg-th-bkg-3' : ''
} font-normal text-th-fgd-1 text-center py-1.5 w-full rounded-none border-r border-th-fgd-4 hover:bg-th-bkg-3 focus:outline-none`}
>
E-mail
</button>
)}
</RadioGroup.Option>
<RadioGroup.Option
value="tg"
className="focus:outline-none flex-1"
>
{({ checked }) => (
<button
className={`${
checked ? 'bg-th-bkg-3 rounded-r' : ''
} font-normal text-th-fgd-1 text-center py-1.5 w-full rounded-none hover:bg-th-bkg-3 focus:outline-none`}
>
Telegram
</button>
)}
</RadioGroup.Option>
</RadioGroup>
</div>
<div className="pb-4">
{alertProvider === 'sms' ? (
<>
<div className={`text-th-fgd-1 pb-2`}>Mobile Number</div>
<PhoneInput
containerClass="w-full"
inputClass="!w-full !bg-th-bkg-1 !rounded !h-10 !text-th-fgd-1
!border !border-th-fgd-4 !border-l hover:!border-th-primary focus:!border-th-primary default-transition"
buttonClass="!bg-th-bkg-2 !border !border-th-fgd-4 !pl-1 hover:!bg-th-bkg-3 focus:!bg-th-primary !rounded-l default-transition"
dropdownClass="!bg-th-bkg-1 !border-0 !pl-1 !text-th-fgd-1 !rounded !mt-2 !max-h-40 thin-scroll"
country="us"
inputProps={{
name: 'phone',
required: true,
// autoFocus: true,
}}
onChange={(val) => setPhoneNumber({ phone: val, code: '' })}
/>
</>
) : null}
{alertProvider === 'mail' ? (
<>
<div className={`text-th-fgd-1 pb-2`}>Email</div>
<Input
value={email}
type="mail"
onChange={(e) => setEmail(e.target.value)}
/>
</>
) : null}
</div>
<Button
disabled={!connected}
onClick={onSubmit}
className="w-full mb-2"
>
{connected ? (
<div className="flex justify-center">
{submitting ? (
<Loading />
) : alertProvider === 'tg' ? (
'Generate Telegram Bot Code'
) : (
'Save Alert'
)}
</div>
) : (
'Connect Wallet To Save'
)}
</Button>
</>
)}
</Modal>
)
}
const TelegramModal = ({
tgCode,
setTgCode,
handleCopyToClipboard,
isCopied,
}) => {
return (
<div className="text-th-fgd-1">
<ElementTitle>Claim Your Alert in Telegram</ElementTitle>
<p className="text-center">This code will expire in 15 minutes</p>
<div className="text-center relative bg-th-bkg-1 py-2 px-8 mt-4 rounded text-lg">
{tgCode}{' '}
<div
className="absolute top-3.5 cursor-pointer right-4 flex items-center text-xs pl-2 text-th-fgd-1 hover:text-th-primary default-transition"
onClick={() => handleCopyToClipboard(tgCode)}
>
<DuplicateIcon className="w-4 h-4 mr-1" />
{isCopied ? 'Copied!' : 'Copy'}
</div>
</div>
<div className="bg-th-bkg-3 p-4 rounded-lg mt-2 mb-4">
<ol className="ml-6 list-decimal space-y-2">
<li>Copy the code above</li>
<li>
Go to{' '}
<a
target="_blank"
rel="noopener noreferrer"
href="https://t.me/mango_alerts_bot"
>
https://t.me/mango_alerts_bot
</a>
</li>
<li>Paste the code and send message</li>
</ol>
</div>
<Button onClick={() => setTgCode('')} className="w-full">
Okay, Got It
</Button>
</div>
)
}

View File

@ -25,7 +25,7 @@ const AlphaModal = ({
<ElementTitle noMarignBottom>Mango Markets UI V2</ElementTitle>
</div>
</Modal.Header>
<div className={`pb-4 px-6 text-th-fgd-2 text-center`}>
<div className={`text-th-fgd-2 text-center`}>
This is an unaudited alpha release of Mango Markets. The software is
provided &apos;AS IS&apos; without warranty of any kind.
<div className={`mt-4 flex justify-center`}>

View File

@ -124,7 +124,7 @@ const DepositModal = ({ isOpen, onClose }) => {
<div className={`text-th-fgd-3 flex-shrink invisible w-5`}>X</div>
<ElementTitle noMarignBottom>Deposit Funds</ElementTitle>
</Modal.Header>
<div className={`pb-6 px-8`}>
<>
<AccountSelect
symbols={symbols}
accounts={depositAccounts}
@ -166,7 +166,7 @@ const DepositModal = ({ isOpen, onClose }) => {
</div>
</Button>
</div>
</div>
</>
</Modal>
)
}

View File

@ -89,7 +89,7 @@ const DepositSrmModal = ({ isOpen, onClose }) => {
<div className={`text-th-fgd-3 flex-shrink invisible w-5`}>X</div>
<ElementTitle noMarignBottom>Contribute SRM</ElementTitle>
</Modal.Header>
<div className={`pb-6 px-8`}>
<>
<AccountSelect
accounts={depositAccounts}
selectedAccount={selectedAccount}
@ -124,7 +124,7 @@ const DepositSrmModal = ({ isOpen, onClose }) => {
</div>
</Button>
</div>
</div>
</>
</Modal>
)
}

View File

@ -1,7 +1,16 @@
import { MarginAccount } from '@blockworks-foundation/mango-client'
import styled from '@emotion/styled'
import { useState } from 'react'
import useMangoStore from '../stores/useMangoStore'
import Select from './Select'
import { abbreviateAddress } from '../utils'
import useMarketList from '../hooks/useMarketList'
const StyledMarginAccountSymbols = styled.div`
:last-child {
border-right-width: 0px;
}
`
type MarginAccountSelectProps = {
className?: string
@ -20,6 +29,7 @@ const MarginAccountSelect = ({
const [selectedMarginAccount, setSelectedMarginAccount] = useState(
value || null
)
const { symbols } = useMarketList()
const handleSelectMarginAccount = (value) => {
const marginAccount = marginAccounts.find(
@ -34,7 +44,16 @@ const MarginAccountSelect = ({
return (
<Select
disabled={disabled}
value={selectedMarginAccount?.publicKey.toString()}
value={
<div className="text-left">
{Object.keys(symbols).map((symbol, index) =>
index !== 0 ? `/${symbol}` : symbol
)}
<div className="text-xs text-th-fgd-4">
{abbreviateAddress(selectedMarginAccount?.publicKey)}
</div>
</div>
}
onChange={handleSelectMarginAccount}
placeholder="Select Margin Account"
className={className}
@ -42,7 +61,10 @@ const MarginAccountSelect = ({
{marginAccounts.length ? (
marginAccounts.map((ma, index) => (
<Select.Option key={index} value={ma.publicKey.toString()}>
{ma.publicKey.toString()}
BTC/ETH/USDT
<div className="text-xs text-th-fgd-4">
{abbreviateAddress(ma.publicKey)}
</div>
</Select.Option>
))
) : (

View File

@ -5,6 +5,8 @@ import useTradeHistory from '../hooks/useTradeHistory'
import useMangoStore from '../stores/useMangoStore'
import FloatingElement from './FloatingElement'
import Tooltip from './Tooltip'
import Button from './Button'
import AlertsModal from './AlertsModal'
const calculatePNL = (tradeHistory, prices, mangoGroup) => {
if (!tradeHistory.length) return '0.00'
@ -53,6 +55,7 @@ const calculatePNL = (tradeHistory, prices, mangoGroup) => {
export default function MarginInfo() {
const connection = useMangoStore((s) => s.connection.current)
const connected = useMangoStore((s) => s.wallet.connected)
const selectedMarginAccount = useMangoStore(
(s) => s.selectedMarginAccount.current
)
@ -67,6 +70,7 @@ export default function MarginInfo() {
}[]
| null
>(null)
const [openAlertModal, setOpenAlertModal] = useState(false)
const tradeHistory = useTradeHistory()
const tradeHistoryLength = useMemo(() => tradeHistory.length, [tradeHistory])
@ -153,21 +157,36 @@ export default function MarginInfo() {
<>
{mAccountInfo
? mAccountInfo.map((entry, i) => (
<div className={`flex justify-between pt-2 pb-2`} key={i}>
<Tooltip content={entry.desc}>
<div
className={`cursor-help font-normal text-th-fgd-4 border-b border-th-fgd-4 border-dashed border-opacity-20 leading-4 default-transition hover:border-th-bkg-2 hover:text-th-fgd-3`}
>
{entry.label}
<>
<div className={`flex justify-between pt-2 pb-2`} key={i}>
<Tooltip content={entry.desc}>
<div
className={`cursor-help font-normal text-th-fgd-4 border-b border-th-fgd-4 border-dashed border-opacity-20 leading-4 default-transition hover:border-th-bkg-2 hover:text-th-fgd-3`}
>
{entry.label}
</div>
</Tooltip>
<div className={`text-th-fgd-1`}>
{entry.currency + entry.value}
{entry.unit}
</div>
</Tooltip>
<div className={`text-th-fgd-1`}>
{entry.currency + entry.value}
{entry.unit}
</div>
</div>
</>
))
: null}
<Button
className="mt-4 w-full"
disabled={!connected}
onClick={() => setOpenAlertModal(true)}
>
Set Liquidation Alert
</Button>
{openAlertModal ? (
<AlertsModal
isOpen={openAlertModal}
onClose={() => setOpenAlertModal(false)}
/>
) : null}
</>
</FloatingElement>
)

View File

@ -29,14 +29,14 @@ const Modal = ({ isOpen, onClose, children, hideClose = false }) => {
{isOpen ? (
<div
className="inline-block bg-th-bkg-2
rounded-lg text-left shadow-lg transform transition-all
rounded-lg text-left px-8 pt-6 pb-8 shadow-lg transform transition-all
sm:my-8 align-middle sm:max-w-md w-full"
>
{!hideClose ? (
<div className="w-full flex justify-end p-2 pb-0">
<div className="">
<button
onClick={onClose}
className={`text-th-fgd-1 hover:text-th-primary focus:outline-none`}
className={`absolute right-2 top-2 text-th-fgd-1 hover:text-th-primary focus:outline-none`}
>
<XIcon className={`h-5 w-5`} />
</button>
@ -55,7 +55,7 @@ const Modal = ({ isOpen, onClose, children, hideClose = false }) => {
const Header = ({ children }) => {
return (
<div className={`flex justify-center bg-th-bkg-2 p-2 pt-0`}>{children}</div>
<div className={`flex justify-center bg-th-bkg-2 pb-4`}>{children}</div>
)
}

View File

@ -28,7 +28,7 @@ const NotificationList = () => {
return (
<div
className={`fixed inset-0 flex items-end px-4 py-6 pointer-events-none sm:p-6 text-th-fgd-1`}
className={`fixed inset-0 flex items-end px-4 py-6 pointer-events-none sm:p-6 text-th-fgd-1 z-50`}
>
<div className={`flex flex-col w-full`}>
{reversedNotifications.map((n, idx) => (

View File

@ -15,12 +15,14 @@ const Select = ({
{({ open }) => (
<>
<Listbox.Button
className={`h-full w-full font-normal bg-th-bkg-1 border border-th-fgd-4 rounded focus:outline-none focus:border-th-primary`}
className={`h-full w-full font-normal bg-th-bkg-1 border border-th-fgd-4 rounded hover:border-th-primary focus:outline-none focus:border-th-primary`}
>
<div
className={`flex items-center justify-between space-x-4 p-3`}
>
<span className="">{value ? value : placeholder}</span>
<span className="text-th-fgd-1">
{value ? value : placeholder}
</span>
{open ? (
<ChevronUpIcon className={`h-5 w-5 mr-1 text-th-primary`} />
) : (
@ -31,7 +33,7 @@ const Select = ({
{open ? (
<Listbox.Options
static
className={`z-20 w-full p-1 absolute left-0 mt-1 bg-th-bkg-1 origin-top-left divide-y divide-th-bkg-3 shadow-lg outline-none rounded-md`}
className={`text-th-fgd-1 z-20 w-full p-1 absolute left-0 mt-1 bg-th-bkg-1 origin-top-left divide-y divide-th-bkg-3 shadow-lg outline-none rounded-md`}
>
{children}
</Listbox.Options>

View File

@ -16,11 +16,11 @@ const Tooltip: FunctionComponent<TooltipProps> = ({
<Tippy
animation="scale"
appendTo={() => document.body}
maxWidth="30rem"
maxWidth="20rem"
interactive
content={
<div
className={`rounded p-3 text-sm bg-th-bkg-3 shadow-md text-th-fgd-2 outline-none focus:outline-none ${className}`}
className={`rounded p-3 text-xs bg-th-bkg-3 leading-5 shadow-md text-th-fgd-3 outline-none focus:outline-none ${className}`}
>
{content}
</div>

View File

@ -16,6 +16,7 @@ import ThemeSwitch from './ThemeSwitch'
import { WalletIcon } from './icons'
import useMangoStore from '../stores/useMangoStore'
import ConnectWalletButton from './ConnectWalletButton'
import { copyToClipboard } from '../utils'
const Code = styled.code`
border: 1px solid hsla(0, 0%, 39.2%, 0.2);
@ -47,12 +48,7 @@ const TopBar = () => {
const handleWalletMenu = (option) => {
if (option === 'Copy address') {
const el = document.createElement('textarea')
el.value = wallet.publicKey.toString()
document.body.appendChild(el)
el.select()
document.execCommand('copy')
document.body.removeChild(el)
copyToClipboard(wallet.publicKey)
setIsCopied(true)
} else {
wallet.disconnect()

View File

@ -29,9 +29,9 @@ export const defaultLayouts = {
{ i: 'marginInfo', x: 6, y: 2, w: 2, h: 12 },
],
lg: [
{ i: 'tvChart', x: 0, y: 0, w: 2, h: 24 },
{ i: 'balanceInfo', x: 2, y: 0, w: 1, h: 13 },
{ i: 'marginInfo', x: 2, y: 1, w: 1, h: 11 },
{ i: 'tvChart', x: 0, y: 0, w: 2, h: 25 },
{ i: 'balanceInfo', x: 2, y: 0, w: 1, h: 12 },
{ i: 'marginInfo', x: 2, y: 1, w: 1, h: 13 },
{ i: 'orderbook', x: 0, y: 2, w: 1, h: 17 },
{ i: 'tradeForm', x: 1, y: 2, w: 1, h: 17 },
{ i: 'marketTrades', x: 2, y: 2, w: 1, h: 17 },

View File

@ -183,7 +183,7 @@ const WithdrawModal = ({ isOpen, onClose }) => {
<Modal.Header>
<ElementTitle noMarignBottom>Withdraw Funds</ElementTitle>
</Modal.Header>
<div className="pb-6 px-8">
<>
<AccountSelect
hideAddress
accounts={withdrawAccounts}
@ -281,7 +281,7 @@ const WithdrawModal = ({ isOpen, onClose }) => {
</div>
</Button>
</div>
</div>
</>
</Modal>
)
}

View File

@ -84,7 +84,7 @@ const WithdrawModal = ({ isOpen, onClose }) => {
<div className={`text-th-fgd-3 flex-shrink invisible w-5`}>X</div>
<ElementTitle noMarignBottom>Withdraw SRM</ElementTitle>
</Modal.Header>
<div className={`pb-6 px-8`}>
<>
<div className={`text-th-fgd-1 pb-2`}>Token Account</div>
<MangoSrmAccountSelector
accounts={mangoSrmAccountsForOwner}
@ -124,7 +124,7 @@ const WithdrawModal = ({ isOpen, onClose }) => {
</div>
</Button>
</div>
</div>
</>
</Modal>
)
}

View File

@ -55,6 +55,14 @@ body {
@apply text-sm font-body tracking-wide;
}
p {
@apply text-th-fgd-3 mb-2.5;
}
a {
@apply text-th-primary transition-all duration-300 hover:opacity-70;
}
button {
transition: all 0.3s ease;
@apply font-semibold rounded-md;
@ -212,6 +220,14 @@ input[type='number'] {
/* phone input */
.react-tel-input .form-control:hover + div {
border-right: 1px solid var(--primary) !important;
}
.react-tel-input .form-control:focus + div {
border-right: 1px solid var(--primary) !important;
}
.react-tel-input .selected-flag:hover {
background-color: transparent !important;
}
@ -225,11 +241,21 @@ input[type='number'] {
}
.react-tel-input .country-list .country:hover {
background-color: var(--bkg-1) !important;
background-color: var(--bkg-3) !important;
}
.react-tel-input .country-list .highlight {
background-color: var(--bkg-1) !important;
background-color: var(--bkg-3) !important;
}
.react-tel-input .country-list {
margin: 8px 0 10px -6px !important;
}
.react-tel-input .selected-flag:hover,
.react-tel-input .selected-flag:focus,
.react-tel-input .selected-flag.open {
background-color: transparent !important;
}
/* orderbook flash */

View File

@ -186,3 +186,12 @@ export const capitalize = (s) => {
if (typeof s !== 'string') return ''
return s.charAt(0).toUpperCase() + s.slice(1)
}
export const copyToClipboard = (copyThis) => {
const el = document.createElement('textarea')
el.value = copyThis.toString()
document.body.appendChild(el)
el.select()
document.execCommand('copy')
document.body.removeChild(el)
}

View File

@ -847,9 +847,9 @@
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
"@blockworks-foundation/mango-client@^0.1.14":
version "0.1.15"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-0.1.15.tgz#9da71650c80012304a61f66ddbdac3b7c7fed390"
"@blockworks-foundation/mango-client@^0.1.16":
version "0.1.16"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-0.1.16.tgz#3bf96836bd5d6310e561ed09de8a8f9c0c72c574"
dependencies:
"@project-serum/serum" "^0.13.20"
"@project-serum/sol-wallet-adapter" "^0.1.4"
@ -2124,9 +2124,9 @@ boolbase@^1.0.0, boolbase@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
"borsh@https://github.com/defactojob/borsh-js#field-mapper":
"borsh@git+https://github.com/defactojob/borsh-js.git#field-mapper":
version "0.3.1"
resolved "https://github.com/defactojob/borsh-js#33a0d24af281112c0a48efb3fa503f3212443de9"
resolved "git+https://github.com/defactojob/borsh-js.git#33a0d24af281112c0a48efb3fa503f3212443de9"
dependencies:
"@types/bn.js" "^4.11.5"
bn.js "^5.0.0"