merge main

This commit is contained in:
saml33 2023-04-24 21:35:55 +10:00
commit 5b8d3eb90b
54 changed files with 761 additions and 146 deletions

23
apis/notifications.ts Normal file
View File

@ -0,0 +1,23 @@
import { NOTIFICATION_API } from 'utils/constants'
export type Notification = {
content: string
createdAt: string
seen: boolean
title: string
id: number
}
export const fetchNotifications = async (wallet: string, token: string) => {
const data = await fetch(`${NOTIFICATION_API}notifications`, {
headers: {
authorization: token,
publickey: wallet,
},
})
const body = await data.json()
if (body.error) {
throw { error: body.error, status: data.status }
}
return body as Notification[]
}

View File

@ -15,7 +15,7 @@ const ThemeSwitcher = () => {
>
{THEMES.map((value) => (
<LinkButton
className={`whitespace-nowrap font-normal no-underline md:hover:text-th-fgd-1 ${
className={`whitespace-nowrap font-normal ${
t(value) === theme ? 'text-th-active' : ''
}`}
onClick={() => setTheme(t(value))}

View File

@ -25,6 +25,7 @@ import { useViewport } from 'hooks/useViewport'
import { breakpoints } from 'utils/theme'
import AccountsButton from './AccountsButton'
import useUnownedAccount from 'hooks/useUnownedAccount'
import NotificationsButton from './notifications/NotificationsButton'
const TopBar = () => {
const { t } = useTranslation('common')
@ -118,7 +119,8 @@ const TopBar = () => {
>{`${t('deposit')} / ${t('withdraw')}`}</Button>
)}
{connected ? (
<div className="flex items-center pr-4 md:pr-0">
<div className="flex items-center">
<NotificationsButton />
<AccountsButton />
<ConnectedMenu />
</div>

View File

@ -522,7 +522,7 @@ const AccountPage = () => {
</p>
</div>
</div>
<div className="col-span-6 border-t border-th-bkg-3 py-3 pl-6 pr-4 md:col-span-3 md:border-l lg:col-span-2 xl:col-span-1 xl:border-t-0">
<div className="col-span-6 border-t border-th-bkg-3 py-3 pl-6 pr-4 md:col-span-3 md:border-l lg:col-span-2 lg:border-l-0 xl:col-span-1 xl:border-l xl:border-t-0">
<div id="account-step-seven" className="flex flex-col items-start">
<div className="flex w-full items-center justify-between">
<Tooltip

View File

@ -13,7 +13,7 @@ const ActionsLinkButton = ({
}) => {
return (
<LinkButton
className="w-full whitespace-nowrap text-left font-normal no-underline md:hover:text-th-fgd-1"
className="w-full whitespace-nowrap text-left font-normal"
disabled={!mangoAccount}
onClick={onClick}
>

View File

@ -85,7 +85,7 @@ const Chat = () => {
</LinkButton>
)}
<div className="bg-th-bkg-2 px-3 py-0.5">
<LinkButton className="mb-0 text-xxs font-normal no-underline">
<LinkButton className="mb-0 text-xxs font-normal">
<span className="text-th-fgd-4">Content Policy</span>
</LinkButton>
</div>

View File

@ -368,9 +368,7 @@ const UserSetupModal = ({
)}
</Button>
<LinkButton onClick={onClose}>
<span className="text-th-fgd-4 underline md:hover:text-th-fgd-3 md:hover:no-underline">
{t('onboarding:skip')}
</span>
</LinkButton>
</div>
</div>
@ -513,9 +511,7 @@ const UserSetupModal = ({
)}
</Button>
<LinkButton onClick={onClose}>
<span className="text-th-fgd-4 underline md:hover:text-th-fgd-3 md:hover:no-underline">
{t('onboarding:skip')}
</span>
</LinkButton>
</UserSetupTransition>
<UserSetupTransition show={depositToken.length === 0}>
@ -547,40 +543,6 @@ const UserSetupModal = ({
</div>
) : null}
</UserSetupTransition>
{/* <UserSetupTransition delay show={showSetupStep === 4}>
{showSetupStep === 4 ? (
<div className="relative">
<h2 className="mb-4 font-display text-3xl tracking-normal md:text-5xl lg:text-6xl">
{t('onboarding:your-profile')}
</h2>
<p className="text-base">{t('onboarding:profile-desc')}</p>
{!showEditProfilePic ? (
<div className="mt-6 border-t border-th-bkg-3 pt-3">
<EditProfileForm
onFinish={onClose}
onEditProfileImage={() => setShowEditProfilePic(true)}
onboarding
/>
<LinkButton className="mt-6" onClick={onClose}>
<span className="text-th-fgd-4 underline md:hover:text-th-fgd-3 md:hover:no-underline">
{t('onboarding:skip-finish')}
</span>
</LinkButton>
</div>
) : null}
<UserSetupTransition show={showEditProfilePic}>
<div
className="thin-scroll absolute mt-6 w-full overflow-auto border-t border-th-bkg-3 px-2 pt-6"
style={{ height: 'calc(100vh - 360px)' }}
>
<EditNftProfilePic
onClose={() => setShowEditProfilePic(false)}
/>
</div>
</UserSetupTransition>
</div>
) : null}
</UserSetupTransition> */}
</div>
<div className="col-span-1 hidden h-screen lg:block">
<ParticlesBackground />

View File

@ -0,0 +1,52 @@
import { useCookies } from 'hooks/notifications/useCookies'
import { useNotifications } from 'hooks/notifications/useNotifications'
import { useMemo, useState } from 'react'
import NotificationsDrawer from './NotificationsDrawer'
import { useToaster } from 'hooks/notifications/useToaster'
import { BellIcon } from '@heroicons/react/20/solid'
import { useIsAuthorized } from 'hooks/notifications/useIsAuthorized'
const NotificationsButton = () => {
useCookies()
useToaster()
const { data, isFetching } = useNotifications()
const isAuth = useIsAuthorized()
const [showDraw, setShowDraw] = useState(false)
const notificationCount = useMemo(() => {
if (!isAuth && !isFetching) {
return 1
} else if (!data || !data.length) {
return 0
} else return data.filter((x) => !x.seen).length
}, [data, isAuth, isFetching])
const toggleModal = () => {
setShowDraw(!showDraw)
}
return (
<>
<button
className="relative flex h-16 w-16 items-center justify-center border-l border-r border-th-bkg-3 focus-visible:bg-th-bkg-3 md:border-r-0 md:hover:bg-th-bkg-2"
onClick={() => toggleModal()}
>
<BellIcon className="h-5 w-5" />
<span className="sr-only">Notifications</span>
{notificationCount !== 0 ? (
<div className="absolute top-4 right-4">
<span className="relative flex h-3.5 w-3.5 items-center justify-center">
<span className="absolute inline-flex h-3.5 w-3.5 animate-ping rounded-full bg-th-down opacity-75"></span>
<span className="relative flex h-3.5 w-3.5 items-center justify-center rounded-full bg-th-down text-xxs font-bold text-th-fgd-1">
{notificationCount}
</span>
</span>
</div>
) : null}
</button>
<NotificationsDrawer isOpen={showDraw} onClose={toggleModal} />
</>
)
}
export default NotificationsButton

View File

@ -0,0 +1,282 @@
import Button, { IconButton, LinkButton } from '@components/shared/Button'
import { Dialog, Transition } from '@headlessui/react'
import {
CalendarIcon,
FaceSmileIcon,
InboxIcon,
TrashIcon,
XMarkIcon,
} from '@heroicons/react/20/solid'
import { bs58 } from '@project-serum/anchor/dist/cjs/utils/bytes'
import { useWallet } from '@solana/wallet-adapter-react'
import { Payload, SIWS } from '@web3auth/sign-in-with-solana'
import { useHeaders } from 'hooks/notifications/useHeaders'
import { useIsAuthorized } from 'hooks/notifications/useIsAuthorized'
import { useNotifications } from 'hooks/notifications/useNotifications'
import { Fragment, useCallback, useEffect, useMemo, useState } from 'react'
import { NOTIFICATION_API } from 'utils/constants'
import NotificationCookieStore from '@store/notificationCookieStore'
import dayjs from 'dayjs'
import { useTranslation } from 'next-i18next'
import { notify } from 'utils/notifications'
const NotificationsDrawer = ({
isOpen,
onClose,
}: {
isOpen: boolean
onClose: () => void
}) => {
const { t } = useTranslation('notifications')
const { data, refetch } = useNotifications()
const wallet = useWallet()
const isAuth = useIsAuthorized()
const headers = useHeaders()
const setCookie = NotificationCookieStore((s) => s.setCookie)
const [isRemoving, setIsRemoving] = useState(false)
const unseenNotifications = useMemo(() => {
if (!data || !data.length) return []
return data.filter((x) => !x.seen)
}, [data])
const markAsSeen = useCallback(
async (ids: number[]) => {
try {
const resp = await fetch(`${NOTIFICATION_API}notifications/seen`, {
method: 'POST',
headers: headers.headers,
body: JSON.stringify({
ids: ids,
seen: true,
}),
})
const body = await resp.json()
const error = body.error
if (error) {
notify({
type: 'error',
title: 'Error',
description: error,
})
return
}
refetch()
} catch (e) {
notify({
type: 'error',
title: 'Error',
description: JSON.stringify(e),
})
}
},
[NOTIFICATION_API, headers]
)
const remove = useCallback(
async (ids: number[]) => {
setIsRemoving(true)
try {
const resp = await fetch(
`${NOTIFICATION_API}notifications/removeForUser`,
{
method: 'POST',
headers: headers.headers,
body: JSON.stringify({
ids: ids,
}),
}
)
const body = await resp.json()
const error = body.error
if (error) {
notify({
type: 'error',
title: 'Error',
description: error,
})
return
}
refetch()
} catch (e) {
notify({
type: 'error',
title: 'Error',
description: JSON.stringify(e),
})
}
setIsRemoving(false)
},
[NOTIFICATION_API, headers]
)
const createSolanaMessage = useCallback(() => {
const payload = new Payload()
payload.domain = window.location.host
payload.address = wallet.publicKey!.toString()
payload.uri = window.location.origin
payload.statement = 'Login to Mango Notifications Admin App'
payload.version = '1'
payload.chainId = 1
const message = new SIWS({ payload })
const messageText = message.prepareMessage()
const messageEncoded = new TextEncoder().encode(messageText)
wallet.signMessage!(messageEncoded)
.then(async (resp) => {
const tokenResp = await fetch(`${NOTIFICATION_API}auth`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
...payload,
signatureString: bs58.encode(resp),
}),
})
const body = await tokenResp.json()
const token = body.token
const error = body.error
if (error) {
notify({
type: 'error',
title: 'Error',
description: error,
})
return
}
setCookie(payload.address, token)
})
.catch((e) => {
notify({
type: 'error',
title: 'Error',
description: e.message ? e.message : `${e}`,
})
})
}, [window.location, wallet, NOTIFICATION_API])
// Mark all notifications as seen when the inbox is opened
useEffect(() => {
if (isOpen && unseenNotifications?.length) {
markAsSeen([...unseenNotifications.map((x) => x.id)])
}
}, [isOpen, unseenNotifications])
return (
<Transition show={isOpen}>
<Dialog className="fixed inset-0 left-0 z-30" onClose={onClose}>
<Transition.Child
enter="transition-opacity ease-linear duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity ease-linear duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
as={Fragment}
>
<div className="fixed inset-0 z-40 cursor-default bg-black bg-opacity-30" />
</Transition.Child>
<Transition.Child
enter="transition ease-in duration-300"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transition ease-out duration-300"
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
as={Fragment}
>
<Dialog.Panel
className={`thin-scroll absolute right-0 z-40 h-full w-full overflow-y-auto bg-th-bkg-1 text-left md:w-96`}
>
<div className="flex h-16 items-center justify-between border-b border-th-bkg-3 pl-6">
<h2 className="text-lg">{t('notifications')}</h2>
<div className="flex items-center">
{data?.length ? (
<LinkButton
disabled={isRemoving}
className="mr-4 flex items-center text-xs"
onClick={() => remove(data.map((n) => n.id))}
>
<TrashIcon className="mr-1 h-3 w-3" />
<span>{t('clear-all')}</span>
</LinkButton>
) : null}
<button
onClick={onClose}
className="flex h-16 w-16 items-center justify-center border-l border-th-bkg-3 text-th-fgd-3 focus-visible:bg-th-bkg-3 md:hover:bg-th-bkg-2"
>
<XMarkIcon className={`h-5 w-5`} />
</button>
</div>
</div>
{isAuth ? (
<>
{data?.length ? (
<>
<div className="space-y-4 border-b border-th-bkg-3 pb-4">
{data?.map((notification) => (
<div
className="border-t border-th-bkg-3 pt-4 first:border-t-0"
key={notification.id}
>
<div className="px-6">
<div className="mb-1 flex items-start justify-between">
<h4 className="mr-4">{notification.title}</h4>
<IconButton
disabled={isRemoving}
onClick={() => remove([notification.id])}
className="mt-1 text-th-fgd-3"
hideBg
>
<TrashIcon className="h-4 w-4" />
</IconButton>
</div>
<div className="mb-2 flex items-center text-th-fgd-4">
<CalendarIcon className="mr-1 h-3.5 w-3.5" />
<p className="text-xs">
{dayjs(notification.createdAt).format(
'DD MMM YYYY, h:mma'
)}
</p>
</div>
<p>{notification.content}</p>
</div>
</div>
))}
</div>
</>
) : (
<div className="relative top-1/2 flex -translate-y-1/2 flex-col justify-center px-6 pb-20">
<div className="flex flex-col items-center justify-center text-center">
<FaceSmileIcon className="mb-2 h-7 w-7 text-th-fgd-2" />
<h3 className="mb-1 text-base">
{t('empty-state-title')}
</h3>
<p>{t('empty-state-desc')}</p>
</div>
</div>
)}
</>
) : (
<div className="relative top-1/2 flex -translate-y-1/2 flex-col justify-center px-6 pb-20">
<div className="flex flex-col items-center justify-center text-center">
<InboxIcon className="mb-2 h-7 w-7 text-th-fgd-2" />
<h3 className="mb-1 text-base">{t('unauth-title')}</h3>
<p>{t('unauth-desc')}</p>
<Button className="mt-6" onClick={createSolanaMessage}>
{t('sign-message')}
</Button>
</div>
</div>
)}
</Dialog.Panel>
</Transition.Child>
</Dialog>
</Transition>
)
}
export default NotificationsDrawer

View File

@ -7,8 +7,8 @@ import {
XMarkIcon,
} from '@heroicons/react/20/solid'
import mangoStore, { CLUSTER } from '@store/mangoStore'
import { Notification, notify } from '../../utils/notifications'
import Loading from './Loading'
import { TransactionNotification, notify } from '../../utils/notifications'
import Loading from '@components/shared/Loading'
import { Transition } from '@headlessui/react'
import {
CLIENT_TX_TIMEOUT,
@ -22,9 +22,9 @@ import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
const setMangoStore = mangoStore.getState().set
const NotificationList = () => {
const TransactionNotificationList = () => {
const { t } = useTranslation()
const notifications = mangoStore((s) => s.notifications)
const transactionNotifications = mangoStore((s) => s.transactionNotifications)
const walletTokens = mangoStore((s) => s.wallet.tokens)
const notEnoughSoLMessage = t('deposit-more-sol')
const [notificationPosition] = useLocalStorageState(
@ -37,11 +37,11 @@ const NotificationList = () => {
// if a notification is shown with {"InstructionError":[0,{"Custom":1}]} then
// add a notification letting the user know they may not have enough SOL
useEffect(() => {
if (notifications.length) {
const customErrorNotification = notifications.find(
if (transactionNotifications.length) {
const customErrorNotification = transactionNotifications.find(
(n) => n.description && n.description.includes('"Custom":1')
)
const notEnoughSolNotification = notifications.find(
const notEnoughSolNotification = transactionNotifications.find(
(n) => n.title && n.title.includes(notEnoughSoLMessage)
)
@ -56,19 +56,19 @@ const NotificationList = () => {
})
}
}
}, [notifications, walletTokens, maxSolDeposit])
}, [transactionNotifications, walletTokens, maxSolDeposit])
const clearAll = useCallback(() => {
setMangoStore((s) => {
const newNotifications = s.notifications.map((n) => ({
const newNotifications = s.transactionNotifications.map((n) => ({
...n,
show: false,
}))
s.notifications = newNotifications
s.transactionNotifications = newNotifications
})
}, [notifications])
}, [transactionNotifications])
const reversedNotifications = [...notifications].reverse()
const reversedNotifications = [...transactionNotifications].reverse()
const getPosition = (position: string) => {
const sharedClasses =
@ -92,7 +92,7 @@ const NotificationList = () => {
return (
<div className={`${getPosition(notificationPosition)} w-full sm:w-auto`}>
{notifications.filter((n) => n.show).length > 1 ? (
{transactionNotifications.filter((n) => n.show).length > 1 ? (
<button
className="pointer-events-auto my-1 flex items-center rounded bg-th-bkg-3 px-2 py-1 text-xs text-th-fgd-3 md:hover:bg-th-bkg-4"
onClick={clearAll}
@ -102,13 +102,17 @@ const NotificationList = () => {
</button>
) : null}
{reversedNotifications.map((n) => (
<Notification key={n.id} notification={n} />
<TransactionNotification key={n.id} notification={n} />
))}
</div>
)
}
const Notification = ({ notification }: { notification: Notification }) => {
const TransactionNotification = ({
notification,
}: {
notification: TransactionNotification
}) => {
const [notificationPosition] = useLocalStorageState(
NOTIFICATION_POSITION_KEY,
'Bottom-Left'
@ -141,20 +145,20 @@ const Notification = ({ notification }: { notification: Notification }) => {
useEffect(() => {
if ((type === 'error' || type === 'success') && txid) {
setMangoStore((s) => {
const newNotifications = s.notifications.map((n) =>
const newNotifications = s.transactionNotifications.map((n) =>
n.txid === txid && n.type === 'confirm' ? { ...n, show: false } : n
)
s.notifications = newNotifications
s.transactionNotifications = newNotifications
})
}
}, [type, txid])
const hideNotification = () => {
setMangoStore((s) => {
const newNotifications = s.notifications.map((n) =>
const newNotifications = s.transactionNotifications.map((n) =>
n.id === id ? { ...n, show: false } : n
)
s.notifications = newNotifications
s.transactionNotifications = newNotifications
})
}
@ -302,4 +306,4 @@ const Notification = ({ notification }: { notification: Notification }) => {
)
}
export default NotificationList
export default TransactionNotificationList

View File

@ -153,7 +153,7 @@ const EditNftProfilePic = ({ onClose }: { onClose: () => void }) => {
{t('save')}
</Button>
{profile?.profile_image_url ? (
<LinkButton className="text-sm" onClick={removeProfileImage}>
<LinkButton onClick={removeProfileImage}>
{t('profile:remove')}
</LinkButton>
) : null}

View File

@ -324,7 +324,7 @@ const Balance = ({ bank }: { bank: BankWithBalance }) => {
{!isUnownedAccount ? (
asPath.includes('/trade') && isBaseOrQuote ? (
<LinkButton
className="font-normal underline-offset-2 md:underline-offset-4"
className="font-normal underline underline-offset-2 md:underline-offset-4 md:hover:no-underline"
onClick={() =>
handleTradeFormBalanceClick(Math.abs(balance), isBaseOrQuote)
}
@ -336,7 +336,7 @@ const Balance = ({ bank }: { bank: BankWithBalance }) => {
</LinkButton>
) : asPath.includes('/swap') ? (
<LinkButton
className="font-normal underline-offset-2 md:underline-offset-4"
className="font-normal underline underline-offset-2 md:underline-offset-4 md:hover:no-underline"
onClick={() =>
handleSwapFormBalanceClick(
Number(formatNumericValue(balance, tokenBank.mintDecimals))

View File

@ -114,7 +114,7 @@ export const LinkButton: FunctionComponent<LinkButtonCombinedProps> = ({
disabled={disabled}
className={`flex items-center border-0 font-bold ${
secondary ? 'text-th-active' : 'text-th-fgd-2'
} rounded-sm underline focus-visible:text-th-active focus-visible:no-underline disabled:cursor-not-allowed disabled:opacity-50 md:hover:text-th-fgd-3 md:hover:no-underline ${className}`}
} rounded-sm focus-visible:text-th-active focus-visible:underline disabled:cursor-not-allowed disabled:opacity-50 ${className} md:hover:text-th-fgd-3`}
{...props}
type="button"
>

View File

@ -19,7 +19,7 @@ const MaxAmountButton = ({
}) => {
return (
<LinkButton
className={`font-normal no-underline ${className} md:hover:text-th-fgd-3`}
className={`font-normal ${className}`}
disabled={disabled}
onClick={onClick}
>

View File

@ -41,14 +41,16 @@ const MangoPerpStatsCharts = () => {
if (!hasDate) {
a.push({
date: c.date_hour,
feeValue: c.fees_accrued,
feeValue: c.total_fees,
})
} else {
hasDate.feeValue = hasDate.feeValue + c.fees_accrued
hasDate.feeValue = hasDate.feeValue + c.total_fees
}
return a
return a.sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
)
}, [])
return values.reverse()
return values
}, [perpStats])
const totalOpenInterestValues = useMemo(() => {
@ -64,9 +66,11 @@ const MangoPerpStatsCharts = () => {
hasDate.openInterest =
hasDate.openInterest + Math.floor(c.open_interest * c.price)
}
return a
return a.sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
)
}, [])
return values.reverse()
return values
}, [perpStats])
return (

View File

@ -6,7 +6,6 @@ import {
} from '@heroicons/react/20/solid'
import { useTranslation } from 'next-i18next'
import Image from 'next/legacy/image'
import { useEffect } from 'react'
import { useViewport } from '../../hooks/useViewport'
import { breakpoints } from '../../utils/theme'
import { LinkButton } from '../shared/Button'
@ -17,7 +16,6 @@ import { NextRouter, useRouter } from 'next/router'
import useJupiterMints from 'hooks/useJupiterMints'
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
import useMangoGroup from 'hooks/useMangoGroup'
import mangoStore from '@store/mangoStore'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import BankAmountWithValue from '@components/shared/BankAmountWithValue'
import useBanksWithBalances from 'hooks/useBanksWithBalances'
@ -25,8 +23,6 @@ import Decimal from 'decimal.js'
const TokenStats = () => {
const { t } = useTranslation(['common', 'token'])
const actions = mangoStore.getState().actions
const initialStatsLoad = mangoStore((s) => s.tokenStats.initialLoad)
const { group } = useMangoGroup()
const { mangoTokens } = useJupiterMints()
const { width } = useViewport()
@ -34,18 +30,6 @@ const TokenStats = () => {
const router = useRouter()
const banks = useBanksWithBalances()
useEffect(() => {
if (group && !initialStatsLoad) {
actions.fetchTokenStats()
}
}, [group])
// const goToTokenPage = (bank: Bank) => {
// router.push(`/token/${bank.name.split(' ')[0].toUpperCase()}`, undefined, {
// shallow: true,
// })
// }
const goToTokenPage = (token: string, router: NextRouter) => {
const query = { ...router.query, ['token']: token }
router.push({ pathname: router.pathname, query })

View File

@ -1,11 +1,12 @@
import mangoStore from '@store/mangoStore'
import { useTranslation } from 'next-i18next'
import dynamic from 'next/dynamic'
import { useMemo, useState } from 'react'
import { useEffect, useMemo, useState } from 'react'
import dayjs from 'dayjs'
import { formatYAxis } from 'utils/formatting'
import useBanksWithBalances from 'hooks/useBanksWithBalances'
import { TokenStatsItem } from 'types'
import useMangoGroup from 'hooks/useMangoGroup'
const DetailedAreaChart = dynamic(
() => import('@components/shared/DetailedAreaChart'),
{ ssr: false }
@ -19,12 +20,21 @@ interface TotalValueItem {
const TotalDepositBorrowCharts = () => {
const { t } = useTranslation(['common', 'token', 'trade'])
const { group } = useMangoGroup()
const tokenStats = mangoStore((s) => s.tokenStats.data)
const initialStatsLoad = mangoStore((s) => s.tokenStats.initialLoad)
const loadingStats = mangoStore((s) => s.tokenStats.loading)
const [borrowDaysToShow, setBorrowDaysToShow] = useState('30')
const [depositDaysToShow, setDepositDaysToShow] = useState('30')
const banks = useBanksWithBalances()
useEffect(() => {
if (group && !initialStatsLoad) {
const actions = mangoStore.getState().actions
actions.fetchTokenStats()
}
}, [group, initialStatsLoad])
const totalDepositBorrowValues = useMemo(() => {
if (!tokenStats) return []
const values: TotalValueItem[] = tokenStats.reduce(
@ -42,11 +52,13 @@ const TotalDepositBorrowCharts = () => {
hasDate.borrowValue =
hasDate.borrowValue + Math.floor(c.total_borrows * c.price)
}
return a
return a.sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
)
},
[]
)
return values.reverse()
return values
}, [tokenStats])
const [currentTotalDepositValue, currentTotalBorrowValue] = useMemo(() => {

View File

@ -204,7 +204,7 @@ const AdvancedMarketHeader = ({
<div className="ml-6 flex items-center space-x-4">
{selectedMarket instanceof PerpMarket ? (
<LinkButton
className="flex items-center whitespace-nowrap text-th-fgd-3 no-underline md:hover:text-th-fgd-4"
className="flex items-center whitespace-nowrap text-th-fgd-3"
onClick={() => setShowMarketDetails(true)}
>
<InformationCircleIcon className="h-5 w-5 flex-shrink-0 md:mr-1.5 md:h-4 md:w-4" />

View File

@ -1,6 +1,7 @@
import React, { useMemo } from 'react'
import React, { useCallback, useMemo } from 'react'
import { Listbox } from '@headlessui/react'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import Decimal from 'decimal.js'
const GroupSize = ({
tickSize,
@ -13,13 +14,20 @@ const GroupSize = ({
onChange: (x: number) => void
className?: string
}) => {
const formatSize = useCallback(
(multiplier: number) => {
return new Decimal(tickSize).mul(multiplier).toNumber()
},
[tickSize]
)
const sizes = useMemo(
() => [
tickSize,
tickSize * 5,
tickSize * 10,
tickSize * 50,
tickSize * 100,
formatSize(5),
formatSize(10),
formatSize(50),
formatSize(100),
],
[tickSize]
)

View File

@ -204,7 +204,7 @@ const MarketCloseModal: FunctionComponent<MarketCloseModalProps> = ({
{submitting ? <Loading /> : <span>{t('trade:close-position')}</span>}
</Button>
<LinkButton
className="inline-flex w-full items-center justify-center text-th-fgd-1"
className="inline-flex w-full items-center justify-center"
onClick={onClose}
>
{t('cancel')}

View File

@ -149,6 +149,7 @@ const PerpPositions = () => {
<p className="flex justify-end">
{isSelectedMarket ? (
<LinkButton
className="font-normal underline underline-offset-2 md:underline-offset-4 md:hover:no-underline"
onClick={() =>
handlePositionClick(floorBasePosition, market)
}
@ -294,7 +295,7 @@ const PerpPositions = () => {
<span className="font-mono text-th-fgd-3">
{isSelectedMarket && asPath === '/trade' ? (
<LinkButton
className="font-normal"
className="font-normal underline underline-offset-2 md:underline-offset-4 md:hover:no-underline"
onClick={() =>
handlePositionClick(floorBasePosition, market)
}

View File

@ -76,11 +76,14 @@ const ConnectedMenu = () => {
<Popover>
<div className="relative">
<Popover.Button
className={`h-16 ${
!isMobile ? 'w-48 border-l border-th-bkg-3 px-4' : ''
} focus:outline-none focus-visible:bg-th-bkg-3 md:hover:bg-th-bkg-2`}
className={`default-transition h-16 ${
!isMobile ? 'w-48 border-l border-th-bkg-3 px-4' : 'w-16'
} hover:bg-th-bkg-2 focus:outline-none focus-visible:bg-th-bkg-3`}
>
<div
className="flex items-center justify-center md:justify-start"
id="account-step-one"
>
<div className="flex items-center" id="account-step-one">
<ProfileImage
imageSize="40"
placeholderSize="24"

View File

@ -0,0 +1,33 @@
import { useWallet } from '@solana/wallet-adapter-react'
import NotificationCookieStore from '@store/notificationCookieStore'
import { useEffect } from 'react'
import { useNotifications } from './useNotifications'
import { notify } from 'utils/notifications'
type Error = {
status: number
error: string
}
export function useCookies() {
const wallet = useWallet()
const updateCookie = NotificationCookieStore((s) => s.updateCookie)
const removeCookie = NotificationCookieStore((s) => s.removeCookie)
const token = NotificationCookieStore((s) => s.currentToken)
const { error } = useNotifications()
const errorResp = error as Error
useEffect(() => {
updateCookie(wallet.publicKey?.toBase58())
}, [wallet.publicKey?.toBase58()])
useEffect(() => {
if (errorResp?.status === 401 && wallet.publicKey && token) {
removeCookie(wallet.publicKey?.toBase58())
notify({
title: errorResp.error,
type: 'error',
})
}
}, [errorResp, wallet.publicKey?.toBase58()])
}

View File

@ -0,0 +1,15 @@
import { useWallet } from '@solana/wallet-adapter-react'
import NotificationCookieStore from '@store/notificationCookieStore'
export function useHeaders() {
const { publicKey } = useWallet()
const token = NotificationCookieStore((s) => s.currentToken)
return {
headers: {
authorization: token,
publickey: publicKey?.toBase58() || '',
'Content-Type': 'application/json',
},
}
}

View File

@ -0,0 +1,15 @@
import { useWallet } from '@solana/wallet-adapter-react'
import { useNotifications } from './useNotifications'
import NotificationCookieStore from '@store/notificationCookieStore'
export function useIsAuthorized() {
const wallet = useWallet()
const { error, isFetched, isLoading } = useNotifications()
const walletPubKey = wallet.publicKey?.toBase58()
const token = NotificationCookieStore((s) => s.currentToken)
const isAuthorized =
walletPubKey && token && !error && isFetched && !isLoading
return isAuthorized
}

View File

@ -0,0 +1,25 @@
import { useQuery } from '@tanstack/react-query'
import { fetchNotifications } from 'apis/notifications'
import NotificationCookieStore from '@store/notificationCookieStore'
import { useWallet } from '@solana/wallet-adapter-react'
//10min
const refetchMs = 600000
export function useNotifications() {
const wallet = useWallet()
const walletPubKey = wallet.publicKey?.toBase58()
const token = NotificationCookieStore((s) => s.currentToken)
const criteria = `${walletPubKey}${token}`
return useQuery(
['notifications', criteria],
() => fetchNotifications(walletPubKey!, token!),
{
enabled: !!(walletPubKey && token),
staleTime: refetchMs,
retry: 1,
refetchInterval: refetchMs,
}
)
}

View File

@ -0,0 +1,28 @@
import usePrevious from '@components/shared/usePrevious'
import { useNotifications } from './useNotifications'
import { useEffect } from 'react'
import { Notification } from '../../apis/notifications'
import { notify } from 'utils/notifications'
export function useToaster() {
const { data } = useNotifications()
const previousData = usePrevious(data)
useEffect(() => {
if (data && data.length && previousData) {
const oldIds = previousData.map((item: Notification) => item.id)
const newObjects = data.filter(
(item: Notification) => !oldIds.includes(item.id)
)
if (newObjects.length) {
newObjects.map((x) =>
notify({
title: 'New message',
description: x.title,
type: 'info',
})
)
}
}
}, [data, previousData])
}

View File

@ -34,6 +34,7 @@
"@tippyjs/react": "4.2.6",
"@types/howler": "2.2.7",
"@types/lodash": "4.14.185",
"@web3auth/sign-in-with-solana": "1.0.0",
"assert": "2.0.0",
"big.js": "6.2.1",
"clsx": "1.2.1",
@ -46,6 +47,7 @@
"html2canvas": "1.4.1",
"http-proxy-middleware": "2.0.6",
"immer": "9.0.12",
"js-cookie": "3.0.1",
"klinecharts": "8.6.3",
"lodash": "4.17.21",
"next": "13.1.0",
@ -76,6 +78,7 @@
"@types/big.js": "6.1.6",
"@types/node": "17.0.23",
"@types/react": "18.0.3",
"@types/js-cookie": "3.0.3",
"@types/react-dom": "18.0.0",
"@types/react-grid-layout": "1.3.2",
"@types/react-window": "1.8.5",

View File

@ -7,6 +7,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
props: {
...(await serverSideTranslations(locale, [
'common',
'notifications',
'profile',
'search',
'settings',

View File

@ -19,7 +19,7 @@ import {
GlowWalletAdapter,
} from '@solana/wallet-adapter-wallets'
import { clusterApiUrl } from '@solana/web3.js'
import Notifications from '../components/shared/Notification'
import TransactionNotification from '@components/notifications/TransactionNotification'
import { ThemeProvider } from 'next-themes'
import { appWithTranslation } from 'next-i18next'
import Layout from '../components/Layout'
@ -118,7 +118,7 @@ function MyApp({ Component, pageProps }: AppProps) {
<Component {...pageProps} />
</Layout>
</ViewportProvider>
<Notifications />
<TransactionNotification />
</ThemeProvider>
</EnhancedWalletProvider>
</WalletProvider>

View File

@ -8,6 +8,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
...(await serverSideTranslations(locale, [
'borrow',
'common',
'notifications',
'onboarding',
'profile',
'search',

View File

@ -27,6 +27,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
props: {
...(await serverSideTranslations(locale, [
'common',
'notifications',
'onboarding',
'profile',
'search',

View File

@ -10,11 +10,12 @@ export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'governance',
'search',
'common',
'governance',
'notifications',
'onboarding',
'profile',
'search',
])),
},
}

View File

@ -8,11 +8,12 @@ export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'governance',
'search',
'common',
'governance',
'notifications',
'onboarding',
'profile',
'search',
])),
},
}

View File

@ -9,6 +9,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
'account',
'activity',
'common',
'notifications',
'onboarding',
'onboarding-tours',
'profile',

View File

@ -8,6 +8,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
...(await serverSideTranslations(locale, [
'common',
'leaderboard',
'notifications',
'profile',
'search',
])),

View File

@ -7,6 +7,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
props: {
...(await serverSideTranslations(locale, [
'common',
'notifications',
'profile',
'search',
])),

View File

@ -12,6 +12,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
props: {
...(await serverSideTranslations(locale, [
'common',
'notifications',
'onboarding',
'profile',
'search',

View File

@ -7,6 +7,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
props: {
...(await serverSideTranslations(locale, [
'common',
'notifications',
'onboarding',
'profile',
'search',

View File

@ -7,6 +7,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
props: {
...(await serverSideTranslations(locale, [
'common',
'notifications',
'onboarding',
'onboarding-tours',
'profile',

View File

@ -16,6 +16,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
props: {
...(await serverSideTranslations(locale, [
'common',
'notifications',
'onboarding',
'onboarding-tours',
'profile',

View File

@ -0,0 +1,9 @@
{
"clear-all": "Clear All",
"empty-state-desc": "You're all up-to-date",
"empty-state-title": "Nothing to see here",
"notifications": "Notifications",
"sign-message": "Sign Message",
"unauth-desc": "You need to verify your wallet to start receiving notifications.",
"unauth-title": "Notifications Inbox"
}

View File

@ -0,0 +1,9 @@
{
"clear-all": "Clear All",
"empty-state-desc": "You're all up-to-date",
"empty-state-title": "Nothing to see here",
"notifications": "Notifications",
"sign-message": "Sign Message",
"unauth-desc": "You need to verify your wallet to start receiving notifications.",
"unauth-title": "Notifications Inbox"
}

View File

@ -0,0 +1,9 @@
{
"clear-all": "Clear All",
"empty-state-desc": "You're all up-to-date",
"empty-state-title": "Nothing to see here",
"notifications": "Notifications",
"sign-message": "Sign Message",
"unauth-desc": "You need to verify your wallet to start receiving notifications.",
"unauth-title": "Notifications Inbox"
}

View File

@ -0,0 +1,9 @@
{
"clear-all": "Clear All",
"empty-state-desc": "You're all up-to-date",
"empty-state-title": "Nothing to see here",
"notifications": "Notifications",
"sign-message": "Sign Message",
"unauth-desc": "You need to verify your wallet to start receiving notifications.",
"unauth-title": "Notifications Inbox"
}

View File

@ -0,0 +1,9 @@
{
"clear-all": "Clear All",
"empty-state-desc": "You're all up-to-date",
"empty-state-title": "Nothing to see here",
"notifications": "Notifications",
"sign-message": "Sign Message",
"unauth-desc": "You need to verify your wallet to start receiving notifications.",
"unauth-title": "Notifications Inbox"
}

View File

@ -1,45 +1,51 @@
<svg width="985" height="1006" viewBox="0 0 985 1006" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1501_16732)">
<path d="M577.996 164.494C511.332 138.229 448.649 139.227 405.047 149.95C519.372 350.391 736.652 216.784 754.428 205.362C713.589 206.589 658.367 196.205 577.996 164.494Z" fill="url(#paint0_linear_1501_16732)"/>
<path d="M577 166.289C657.369 198 713.659 206.589 754.467 205.362L755.643 204.604C603.604 -54.75 404.799 149.441 404.799 149.441L405.088 149.95C447.971 142.349 510.295 139.985 577 166.289Z" fill="url(#paint1_linear_1501_16732)"/>
<path d="M566.586 652.388C524.691 752.539 444.25 818.854 346.794 828.9C344.698 829.179 318.025 831.283 303.302 830.196C418.894 914.705 555.265 928.82 555.265 928.82C584.461 931.812 617.509 934.266 650.677 933.398C662.356 902.905 668.931 867.992 667.045 828.38C662.666 736.38 716.013 689.147 773.78 664.917C744.173 619.141 722.079 563.7 706.946 511.798C661.839 523.799 604.55 561.535 566.586 652.388Z" fill="url(#paint2_linear_1501_16732)"/>
<path d="M665.218 827.73C667.114 867.342 659.173 903.083 647.493 933.578C733.13 931.323 822.737 906.874 845.61 813.825C850.458 794.084 851.076 772.628 843.017 753.975C835.155 735.8 820.004 722.054 806.557 707.511C793.977 693.614 782.571 678.701 772.451 662.922C714.736 687.161 660.84 735.74 665.218 827.73Z" fill="url(#paint3_linear_1501_16732)"/>
<path d="M703.962 501.554C690.805 466.342 679.604 431.09 662.577 398.303C656.882 387.237 650.443 376.573 643.305 366.382C626.088 341.981 607.763 318.222 585.737 297.991C547.831 298.101 508.221 291.738 472.021 273.164C435.351 325.175 393.396 413.403 431.8 514.114C488.509 662.843 373.427 764.49 299.57 827.423L303.301 830.196C317.149 831.276 331.066 831.076 344.878 829.597C442.325 819.542 527.423 751.632 569.318 651.491C607.294 560.638 662.536 526.124 707.585 514.114C706.313 509.924 705.108 505.737 703.962 501.554Z" fill="url(#paint4_linear_1501_16732)"/>
<path d="M220.976 303.487C164.517 365.333 134.751 452.307 135.779 537.185C136.367 586.573 149.365 633.036 170.692 675.72C173.89 682.124 177.282 688.441 180.866 694.672C299.561 575.529 256.677 398.002 220.976 303.487Z" fill="url(#paint5_linear_1501_16732)"/>
<path d="M433.795 514.112C395.391 413.364 436.41 325.713 473.029 273.712C448.88 261.279 427.397 244.245 409.786 223.566C381.255 223.019 352.841 227.362 325.775 236.405C282.603 250.699 246.134 275.807 216.956 307.767C251.869 400.207 293.825 573.864 177.754 690.393C199.799 728.898 228.627 763.97 261.026 794.893C273.805 807.076 287.346 818.435 301.565 828.899C375.421 765.996 490.504 662.842 433.795 514.112Z" fill="url(#paint6_linear_1501_16732)"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M472.746 272.289C472.751 272.283 472.755 272.276 472.76 272.27C509.05 290.89 548.76 297.27 586.76 297.16C608.84 317.44 627.21 341.26 644.47 365.72C651.627 375.937 658.082 386.628 663.79 397.72C676.989 423.136 686.696 450.028 696.518 477.237C699.398 485.217 702.289 493.225 705.28 501.23C706.217 504.656 707.196 508.084 708.217 511.514L708.27 511.5C723.44 563.53 745.59 619.11 775.27 665L775.241 665.012C785.058 680.087 796.055 694.361 808.13 707.7C810.506 710.27 812.936 712.816 815.369 715.365L815.37 715.366L815.371 715.367L815.374 715.37C826.742 727.28 838.19 739.274 844.68 754.28C852.76 772.98 852.14 794.49 847.28 814.28C824.35 907.56 734.52 932.07 648.67 934.33L648.711 934.223C616.522 934.864 584.556 932.465 556.21 929.56C556.21 929.56 419.5 915.41 303.62 830.69L299.88 827.91L299.881 827.909C286.355 817.83 273.451 806.941 261.24 795.3C228.76 764.3 199.86 729.14 177.76 690.54C177.908 690.392 178.055 690.243 178.203 690.095C175.587 685.388 173.079 680.633 170.68 675.83C149.3 633.04 136.27 586.46 135.68 536.95C134.674 453.873 163.095 368.795 217.118 307.113C217.098 307.062 217.079 307.011 217.06 306.96C246.31 274.92 282.87 249.75 326.15 235.42C353.283 226.354 381.768 222.001 410.37 222.55C427.775 242.987 448.954 259.874 472.746 272.289ZM406.153 815.85C425.711 808.711 444.24 799.11 461.518 787.279C444.131 799.029 425.575 808.637 406.153 815.85Z" fill="url(#paint0_linear_1501_16722)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M756.498 204.628C756.512 204.628 756.526 204.628 756.54 204.627L757.72 203.867C605.3 -56.1329 406 148.567 406 148.567L406.285 149.068C406.273 149.071 406.262 149.074 406.25 149.077C520.856 350.01 738.664 216.087 756.498 204.628Z" fill="url(#paint1_linear_1501_16722)"/>
<path d="M567.56 652.44C525.56 752.84 444.92 819.32 347.22 829.39C345.12 829.67 318.38 831.78 303.62 830.69C419.5 915.41 556.21 929.56 556.21 929.56C585.48 932.56 618.61 935.02 651.86 934.15C663.57 903.58 670.16 868.58 668.27 828.87C663.88 736.64 717.36 689.29 775.27 665C745.59 619.11 723.44 563.53 708.27 511.5C663.05 523.53 605.62 561.36 567.56 652.44Z" fill="url(#paint2_linear_1501_16722)"/>
<path d="M666.44 828.22C668.34 867.93 660.38 903.76 648.67 934.33C734.52 932.07 824.35 907.56 847.28 814.28C852.14 794.49 852.76 772.98 844.68 754.28C836.8 736.06 821.61 722.28 808.13 707.7C795.519 693.769 784.084 678.818 773.94 663C716.08 687.3 662.05 736 666.44 828.22Z" fill="url(#paint3_linear_1501_16722)"/>
<path d="M705.28 501.23C692.09 465.93 680.86 430.59 663.79 397.72C658.082 386.628 651.627 375.937 644.47 365.72C627.21 341.26 608.84 317.44 586.76 297.16C548.76 297.27 509.05 290.89 472.76 272.27C436 324.41 393.94 412.86 432.44 513.82C489.29 662.92 373.92 764.82 299.88 827.91L303.62 830.69C317.502 831.773 331.455 831.573 345.3 830.09C442.99 820.01 528.3 751.93 570.3 651.54C608.37 560.46 663.75 525.86 708.91 513.82C707.637 509.62 706.427 505.423 705.28 501.23Z" fill="url(#paint4_linear_1501_16722)"/>
<path d="M221.09 302.67C164.49 364.67 134.65 451.86 135.68 536.95C136.27 586.46 149.3 633.04 170.68 675.83C173.887 682.25 177.287 688.583 180.88 694.83C299.87 575.39 256.88 397.42 221.09 302.67Z" fill="url(#paint5_linear_1501_16722)"/>
<path d="M434.44 513.82C395.94 412.82 437.06 324.95 473.77 272.82C449.561 260.357 428.024 243.28 410.37 222.55C381.768 222.001 353.283 226.354 326.15 235.42C282.87 249.75 246.31 274.92 217.06 306.96C252.06 399.63 294.12 573.72 177.76 690.54C199.86 729.14 228.76 764.3 261.24 795.3C274.051 807.513 287.625 818.899 301.88 829.39C375.92 766.33 491.29 662.92 434.44 513.82Z" fill="url(#paint6_linear_1501_16722)"/>
<path d="M578 165.13C658.57 196.92 715 205.53 755.91 204.3L757.09 203.54C604.67 -56.4601 405.37 148.24 405.37 148.24L405.66 148.75C448.65 141.13 511.13 138.76 578 165.13Z" fill="url(#paint7_linear_1501_16722)"/>
<path d="M579 163.33C512.17 137 449.33 138 405.62 148.75C520.23 349.69 738.05 215.75 755.87 204.3C714.93 205.53 659.57 195.12 579 163.33Z" fill="url(#paint8_linear_1501_16722)"/>
<defs>
<linearGradient id="paint0_linear_1501_16732" x1="433.489" y1="200.923" x2="716.471" y2="237.75" gradientUnits="userSpaceOnUse">
<stop stop-color="#AFD803"/>
<stop offset="1" stop-color="#6CBF00"/>
<linearGradient id="paint0_linear_1501_16722" x1="46.5001" y1="344.5" x2="978.5" y2="903" gradientUnits="userSpaceOnUse">
<stop stop-color="#E54033"/>
<stop offset="0.489583" stop-color="#FECA1A"/>
<stop offset="1" stop-color="#AFD803"/>
</linearGradient>
<linearGradient id="paint1_linear_1501_16732" x1="424.179" y1="82.488" x2="788.603" y2="216.813" gradientUnits="userSpaceOnUse">
<linearGradient id="paint1_linear_1501_16722" x1="263767" y1="31225.5" x2="205421" y2="-28791.6" gradientUnits="userSpaceOnUse">
<stop offset="0.15" stop-color="#6CBF00"/>
<stop offset="1" stop-color="#AFD803"/>
</linearGradient>
<linearGradient id="paint2_linear_1501_16732" x1="207.351" y1="837.218" x2="789.9" y2="695.572" gradientUnits="userSpaceOnUse">
<linearGradient id="paint2_linear_1501_16722" x1="207.43" y1="837.73" x2="791.43" y2="695.73" gradientUnits="userSpaceOnUse">
<stop offset="0.21" stop-color="#E54033"/>
<stop offset="0.84" stop-color="#FECA1A"/>
</linearGradient>
<linearGradient id="paint3_linear_1501_16732" x1="666.315" y1="797.926" x2="846.068" y2="799.273" gradientUnits="userSpaceOnUse">
<linearGradient id="paint3_linear_1501_16722" x1="667.54" y1="798.34" x2="847.74" y2="799.69" gradientUnits="userSpaceOnUse">
<stop stop-color="#FECA1A"/>
<stop offset="0.4" stop-color="#FECA1A"/>
<stop offset="1" stop-color="#AFD803"/>
</linearGradient>
<linearGradient id="paint4_linear_1501_16732" x1="259.44" y1="840.849" x2="627.971" y2="341.923" gradientUnits="userSpaceOnUse">
<linearGradient id="paint4_linear_1501_16722" x1="259.65" y1="841.37" x2="629.1" y2="341.2" gradientUnits="userSpaceOnUse">
<stop offset="0.16" stop-color="#E54033"/>
<stop offset="0.84" stop-color="#FECA1A"/>
</linearGradient>
<linearGradient id="paint5_linear_1501_16732" x1="205.774" y1="345.103" x2="189.455" y2="667.361" gradientUnits="userSpaceOnUse">
<linearGradient id="paint5_linear_1501_16722" x1="205.85" y1="344.39" x2="189.49" y2="667.45" gradientUnits="userSpaceOnUse">
<stop stop-color="#FECA1A"/>
<stop offset="0.76" stop-color="#E54033"/>
</linearGradient>
<linearGradient id="paint6_linear_1501_16732" x1="386.054" y1="261.422" x2="287.63" y2="635.161" gradientUnits="userSpaceOnUse">
<linearGradient id="paint6_linear_1501_16722" x1="386.58" y1="260.5" x2="287.91" y2="635.17" gradientUnits="userSpaceOnUse">
<stop offset="0.16" stop-color="#FECA1A"/>
<stop offset="1" stop-color="#E54033"/>
</linearGradient>
<clipPath id="clip0_1501_16732">
<rect width="715.21" height="860.578" fill="white" transform="translate(135 73)"/>
</clipPath>
<linearGradient id="paint7_linear_1501_16722" x1="424.8" y1="81.1199" x2="790.13" y2="215.78" gradientUnits="userSpaceOnUse">
<stop offset="0.15" stop-color="#6CBF00"/>
<stop offset="1" stop-color="#AFD803"/>
</linearGradient>
<linearGradient id="paint8_linear_1501_16722" x1="434.133" y1="199.85" x2="717.819" y2="236.768" gradientUnits="userSpaceOnUse">
<stop stop-color="#AFD803"/>
<stop offset="1" stop-color="#6CBF00"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -21,7 +21,7 @@ import {
} from '@blockworks-foundation/mango-v4'
import EmptyWallet from '../utils/wallet'
import { Notification, notify } from '../utils/notifications'
import { TransactionNotification, notify } from '../utils/notifications'
import {
getNFTsByOwner,
getTokenAccountsByOwnerWithWrappedSol,
@ -159,8 +159,8 @@ export type MangoStore = {
}
mangoAccounts: MangoAccount[]
markets: Serum3Market[] | undefined
notificationIdCounter: number
notifications: Array<Notification>
transactionNotificationIdCounter: number
transactionNotifications: Array<TransactionNotification>
perpMarkets: PerpMarket[]
perpStats: {
loading: boolean
@ -305,8 +305,8 @@ const mangoStore = create<MangoStore>()(
},
mangoAccounts: [],
markets: undefined,
notificationIdCounter: 0,
notifications: [],
transactionNotificationIdCounter: 0,
transactionNotifications: [],
perpMarkets: [],
perpStats: {
loading: false,

View File

@ -0,0 +1,60 @@
import produce from 'immer'
import Cookies from 'js-cookie'
import create from 'zustand'
type ICookieStore = {
currentToken: string
set: (x: (x: ICookieStore) => void) => void
updateCookie: (wallet?: string) => void
removeCookie: (wallet: string) => void
setCookie: (wallet: string, token: string) => void
}
const CookieStore = create<ICookieStore>((set, get) => ({
currentToken: '',
set: (fn) => set(produce(fn)),
updateCookie: async (wallet?: string) => {
const set = get().set
const token = wallet ? getWalletToken(wallet) : ''
set((state) => {
state.currentToken = token
})
},
removeCookie: async (wallet: string) => {
const set = get().set
if (getWalletToken(wallet)) {
removeWalletToken(wallet)
set((state) => {
state.currentToken = ''
})
}
},
setCookie: async (wallet: string, token: string) => {
const set = get().set
setWalletToken(wallet, token)
set((state) => {
state.currentToken = token
})
},
}))
export default CookieStore
const cookieName = 'authToken-'
const getWalletToken = (wallet: string) => {
const token = Cookies.get(`${cookieName}${wallet}`)
return token || ''
}
const removeWalletToken = (wallet: string) => {
Cookies.remove(`${cookieName}${wallet}`)
}
const setWalletToken = (wallet: string, token: string) => {
Cookies.set(`${cookieName}${wallet}`, token, {
secure: true,
sameSite: 'strict',
expires: 360,
})
}

View File

@ -416,7 +416,8 @@ svg {
h1,
h2,
h3 {
h3,
h4 {
@apply font-bold text-th-fgd-1;
}

View File

@ -288,6 +288,7 @@ export interface NFT {
export interface PerpStatsItem {
date_hour: string
fees_accrued: number
fees_settled: number
funding_rate_hourly: number
instantaneous_funding_rate: number
mango_group: string
@ -296,6 +297,7 @@ export interface PerpStatsItem {
perp_market: string
price: number
stable_price: number
total_fees: number
}
export type ActivityFeed = {

View File

@ -87,4 +87,7 @@ export const TRADE_VOLUME_ALERT_KEY = 'tradeVolumeAlert-0.1'
export const PAGINATION_PAGE_LENGTH = 250
export const JUPITER_API_MAINNET = 'https://token.jup.ag/strict'
export const JUPITER_API_DEVNET = 'https://api.jup.ag/api/tokens/devnet'
export const NOTIFICATION_API = 'https://notifications-api.herokuapp.com/'

View File

@ -3,7 +3,7 @@ import mangoStore from '@store/mangoStore'
import { Howl } from 'howler'
import { SOUND_SETTINGS_KEY } from './constants'
export type Notification = {
export type TransactionNotification = {
type: 'success' | 'info' | 'error' | 'confirm'
title: string
description?: null | string
@ -29,8 +29,8 @@ export function notify(newNotification: {
noSound?: boolean
}) {
const setMangoStore = mangoStore.getState().set
const notifications = mangoStore.getState().notifications
const lastId = mangoStore.getState().notificationIdCounter
const notifications = mangoStore.getState().transactionNotifications
const lastId = mangoStore.getState().transactionNotificationIdCounter
const newId = lastId + 1
const savedSoundSettings = localStorage.getItem(SOUND_SETTINGS_KEY)
const soundSettings = savedSoundSettings
@ -53,7 +53,7 @@ export function notify(newNotification: {
}
}
const newNotif: Notification = {
const newNotif: TransactionNotification = {
id: newId,
type: 'success',
show: true,
@ -62,7 +62,7 @@ export function notify(newNotification: {
}
setMangoStore((state) => {
state.notificationIdCounter = newId
state.notifications = [...notifications, newNotif]
state.transactionNotificationIdCounter = newId
state.transactionNotifications = [...notifications, newNotif]
})
}

View File

@ -2157,6 +2157,11 @@
dependencies:
"@types/node" "*"
"@types/js-cookie@3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.3.tgz#d6bfbbdd0c187354ca555213d1962f6d0691ff4e"
integrity sha512-Xe7IImK09HP1sv2M/aI+48a20VX+TdRJucfq4vfRVy6nWN8PYPOEnlMRSgxJAgYQIXJVL8dZ4/ilAM7dWNaOww==
"@types/json-schema@^7.0.9":
version "7.0.11"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
@ -2669,6 +2674,16 @@
"@walletconnect/window-getters" "^1.0.1"
tslib "1.14.1"
"@web3auth/sign-in-with-solana@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@web3auth/sign-in-with-solana/-/sign-in-with-solana-1.0.0.tgz#f7fedff74bc47782fb84fc176dc942095e81eb1f"
integrity sha512-YwgROXLDfmaqk9lh6hLO/VSKR32FxftOd01aLUMic4yM1IQ00/hx0/12phzE5gn3uiuBZVoHv6yWEAFmnfxh2Q==
dependencies:
"@stablelib/random" "^1.0.1"
bs58 "^5.0.0"
tweetnacl "^1.0.3"
valid-url "^1.0.9"
JSONStream@^1.3.5:
version "1.3.5"
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
@ -5754,6 +5769,11 @@ js-base64@^3.7.2:
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.5.tgz#21e24cf6b886f76d6f5f165bfcd69cc55b9e3fca"
integrity sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==
js-cookie@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414"
integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==
js-sha256@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966"
@ -8340,6 +8360,11 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==
tweetnacl@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596"
integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
@ -8483,6 +8508,11 @@ v8-compile-cache@^2.0.3:
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
valid-url@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200"
integrity sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==
verror@1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"