mango-ui-v3/pages/referral.tsx

545 lines
23 KiB
TypeScript
Raw Normal View History

2022-02-14 09:17:11 -08:00
import { useEffect, useState, useCallback } from 'react'
2022-02-14 09:15:28 -08:00
import PageBodyContainer from '../components/PageBodyContainer'
import TopBar from '../components/TopBar'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import useMangoStore from '../stores/useMangoStore'
2022-02-14 09:15:46 -08:00
import {
mangoCacheSelector,
2022-02-14 09:15:46 -08:00
mangoClientSelector,
mangoGroupConfigSelector,
mangoGroupSelector,
walletSelector,
} from '../stores/selectors'
2022-02-14 11:36:04 -08:00
import { IconButton } from '../components/Button'
2022-02-10 17:50:40 -08:00
import { abbreviateAddress, copyToClipboard } from '../utils'
2022-02-14 09:15:46 -08:00
import { notify } from '../utils/notifications'
2022-02-14 09:17:11 -08:00
import {
getMarketIndexBySymbol,
ReferrerIdRecord,
} from '@blockworks-foundation/mango-client'
2022-02-10 15:48:14 -08:00
import { useTranslation } from 'next-i18next'
import EmptyState from '../components/EmptyState'
import {
CheckIcon,
CurrencyDollarIcon,
DuplicateIcon,
LinkIcon,
} from '@heroicons/react/outline'
import { MngoMonoIcon } from '../components/icons'
import Link from 'next/link'
import { Table, Td, Th, TrBody, TrHead } from '../components/TableElements'
import dayjs from 'dayjs'
import AccountsModal from '../components/AccountsModal'
2022-02-10 17:50:40 -08:00
import { useViewport } from '../hooks/useViewport'
import { breakpoints } from '../components/TradePageGrid'
import { ExpandableRow } from '../components/TableElements'
import MobileTableHeader from '../components/mobile/MobileTableHeader'
2022-02-10 18:29:07 -08:00
import Input from '../components/Input'
import InlineNotification from '../components/InlineNotification'
import useMangoAccount from '../hooks/useMangoAccount'
2022-02-14 09:15:28 -08:00
export async function getStaticProps({ locale }) {
return {
props: {
2022-02-19 11:36:29 -08:00
...(await serverSideTranslations(locale, ['common', 'referrals'])),
2022-02-14 09:15:28 -08:00
// Will be passed to the page component as props
},
}
}
const referralHistory = []
// [
// {
// time: '2022-02-09T19:28:59Z',
// referralLink: 'test2',
// referee: '22JS1jkvkLcdxhHo1LpWXUh6sTErkt54j1YaszYWZoCi',
// fee: 0.22,
// },
// {
// time: '2022-02-08T19:28:59Z',
// referralLink: 'test2',
// referee: '22JS1jkvkLcdxhHo1LpWXUh6sTErkt54j1YaszYWZoCi',
// fee: 0.21,
// },
// {
// time: '2022-02-07T19:28:59Z',
// referralLink: 'test2',
// referee: '22JS1jkvkLcdxhHo1LpWXUh6sTErkt54j1YaszYWZoCi',
// fee: 0.15,
// },
// ]
2022-02-10 15:48:14 -08:00
const ProgramDetails = () => {
2022-02-19 11:36:29 -08:00
const { t } = useTranslation('referrals')
return (
<>
2022-02-19 11:36:29 -08:00
<h2 className="mb-4">{t('referrals:program-details')}</h2>
<ul className="list-disc pl-3">
2022-02-19 11:36:29 -08:00
<li>{t('referrals:program-details-1')}</li>
<li>{t('referrals:program-details-2')}</li>
<li>{t('referrals:program-details-3')}</li>
<li>{t('referrals:program-details-4')}</li>
</ul>
</>
)
}
2022-02-14 09:15:46 -08:00
export default function Referral() {
2022-02-19 11:36:29 -08:00
const { t } = useTranslation(['common', 'referrals'])
2022-02-14 09:15:28 -08:00
const mangoGroup = useMangoStore(mangoGroupSelector)
const mangoCache = useMangoStore(mangoCacheSelector)
const { mangoAccount } = useMangoAccount()
2022-02-14 09:15:46 -08:00
const groupConfig = useMangoStore(mangoGroupConfigSelector)
const client = useMangoStore(mangoClientSelector)
const wallet = useMangoStore(walletSelector)
2022-02-10 15:48:14 -08:00
const connected = useMangoStore((s) => s.wallet.connected)
2022-02-14 09:15:28 -08:00
2022-02-14 09:17:11 -08:00
const [customRefLinkInput, setCustomRefLinkInput] = useState('')
const [existingCustomRefLinks, setexistingCustomRefLinks] = useState<
ReferrerIdRecord[]
>([])
2022-02-10 15:48:14 -08:00
const [hasCopied, setHasCopied] = useState(null)
const [showAccountsModal, setShowAccountsModal] = useState(false)
// const [hasReferrals] = useState(false) // Placeholder to show/hide users referral stats
2022-02-10 15:48:14 -08:00
const [loading, setLoading] = useState(false)
2022-02-10 18:29:07 -08:00
const [inputError, setInputError] = useState('')
2022-02-10 17:50:40 -08:00
const { width } = useViewport()
const isMobile = width ? width < breakpoints.sm : false
2022-02-14 09:17:11 -08:00
const fetchCustomReferralLinks = useCallback(async () => {
2022-02-10 15:48:14 -08:00
setLoading(true)
2022-02-14 09:17:11 -08:00
const referrerIds = await client.getReferrerIdsForMangoAccount(mangoAccount)
2022-02-14 09:15:46 -08:00
2022-02-10 15:48:14 -08:00
setexistingCustomRefLinks(referrerIds)
setLoading(false)
2022-02-14 09:17:11 -08:00
}, [mangoAccount])
2022-02-14 09:15:46 -08:00
2022-02-14 09:17:11 -08:00
useEffect(() => {
if (mangoAccount) {
fetchCustomReferralLinks()
}
}, [mangoAccount])
2022-02-14 09:15:46 -08:00
2022-02-10 15:48:14 -08:00
useEffect(() => {
let timer
if (hasCopied) {
timer = setTimeout(() => setHasCopied(null), 1000)
}
return () => {
clearTimeout(timer)
}
}, [hasCopied])
2022-02-10 18:29:07 -08:00
const onChangeRefIdInput = (value) => {
2022-02-10 18:33:55 -08:00
const id = value.replace(/ /g, '')
setCustomRefLinkInput(id)
if (id.length > 32) {
2022-02-19 11:36:29 -08:00
setInputError(t('referrals:too-long-error'))
2022-02-10 18:29:07 -08:00
} else {
setInputError('')
}
}
const validateRefIdInput = () => {
if (customRefLinkInput.length >= 33) {
2022-02-19 11:36:29 -08:00
setInputError(t('referrals:too-long-error'))
2022-02-10 18:29:07 -08:00
}
if (customRefLinkInput.length === 0) {
2022-02-19 11:36:29 -08:00
setInputError(t('referrals:enter-refferal-id'))
2022-02-14 09:15:28 -08:00
}
2022-02-14 09:15:46 -08:00
}
2022-02-10 18:29:07 -08:00
const submitRefLink = async () => {
if (!inputError) {
try {
const txid = await client.registerReferrerId(
mangoGroup,
mangoAccount,
wallet,
customRefLinkInput
)
notify({
txid,
2022-02-19 11:36:29 -08:00
title: t('referrals:link-created'),
2022-02-10 18:29:07 -08:00
})
fetchCustomReferralLinks()
2022-02-10 18:29:07 -08:00
} catch (e) {
notify({
type: 'error',
2022-02-19 11:36:29 -08:00
title: t('referrals:link-not-created'),
2022-02-10 18:29:07 -08:00
description: e.message,
txid: e.txid,
})
}
} else return
}
2022-02-10 15:48:14 -08:00
const handleCopyLink = (link, index) => {
copyToClipboard(link)
setHasCopied(index)
}
2022-02-14 09:15:46 -08:00
const mngoIndex = getMarketIndexBySymbol(groupConfig, 'MNGO')
2022-02-14 09:15:46 -08:00
const hasRequiredMngo =
mangoGroup && mangoAccount
2022-02-14 11:36:04 -08:00
? mangoAccount
.getUiDeposit(
mangoCache.rootBankCache[mngoIndex],
mangoGroup,
mngoIndex
)
2022-02-14 14:23:57 -08:00
.toNumber() >= 10000
2022-02-14 09:15:46 -08:00
: false
2022-02-14 09:17:11 -08:00
const hasCustomRefLinks =
2022-02-10 15:48:14 -08:00
existingCustomRefLinks && existingCustomRefLinks.length > 0
2022-02-14 09:15:28 -08:00
return (
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
<TopBar />
<PageBodyContainer>
2022-02-10 15:48:14 -08:00
<div className="py-4 md:pb-4 md:pt-10">
<h1 className={`mb-1 text-th-fgd-1 text-2xl font-semibold`}>
2022-02-19 11:36:29 -08:00
{t('referrals:sow-seed')}
2022-02-10 15:48:14 -08:00
</h1>
2022-02-10 17:50:40 -08:00
<div className="flex flex-col sm:flex-row items-start">
2022-02-19 11:36:29 -08:00
<p className="mb-0 mr-2 text-th-fgd-1">{t('referrals:earn-16')}</p>
2022-02-14 09:15:46 -08:00
</div>
2022-02-10 15:48:14 -08:00
</div>
<div className="bg-th-bkg-2 grid grid-cols-12 grid-flow-row gap-x-6 gap-y-8 p-4 sm:p-6 rounded-lg">
{connected ? (
mangoAccount ? (
<>
{/* {hasReferrals ? (
2022-02-10 15:48:14 -08:00
<div className="col-span-12">
2022-02-19 11:36:29 -08:00
<h2 className="mb-4">{t('referrals:your-referrals')}</h2>
2022-02-10 17:50:40 -08:00
<div className="border-b border-th-bkg-4 sm:border-b-0 grid grid-cols-2 grid-row-flow sm:gap-6">
<div className="sm:border-b border-t border-th-bkg-4 col-span-2 sm:col-span-1 p-3 sm:p-4">
2022-02-10 15:48:14 -08:00
<div className="pb-0.5 text-th-fgd-3 text-xs sm:text-sm">
2022-02-19 11:36:29 -08:00
{t('referrals:total-earnings')}
2022-02-10 15:48:14 -08:00
</div>
<div className="font-bold text-th-fgd-1 text-xl sm:text-2xl">
$150.50
</div>
</div>
2022-02-10 17:50:40 -08:00
<div className="sm:border-b border-t border-th-bkg-4 col-span-2 sm:col-span-1 p-3 sm:p-4">
2022-02-10 15:48:14 -08:00
<div className="pb-0.5 text-th-fgd-3 text-xs sm:text-sm">
2022-02-19 11:36:29 -08:00
{t('referrals:total-referrals')}
2022-02-10 15:48:14 -08:00
</div>
<div className="font-bold text-th-fgd-1 text-xl sm:text-2xl">
15
</div>
</div>
</div>
2022-02-14 09:15:46 -08:00
</div>
) : null} */}
2022-02-10 15:48:14 -08:00
<div className="col-span-12">
2022-02-14 11:36:04 -08:00
<div className="flex flex-col xl:flex-row xl:space-x-6 space-y-4 xl:space-y-0 w-full">
<div className="min-w-[25%] bg-th-bkg-3 flex-1 p-6 rounded-md">
<ProgramDetails />
2022-02-14 11:36:04 -08:00
</div>
2022-02-10 15:48:14 -08:00
<div className="flex flex-col w-full">
{hasRequiredMngo ? (
<div className="bg-th-bkg-3 flex-1 p-6 rounded-md">
2022-02-19 11:36:29 -08:00
<h2 className="mb-4">{t('referrals:your-links')}</h2>
2022-02-10 15:48:14 -08:00
{!loading ? (
!hasCustomRefLinks ? (
<Table>
<thead>
<TrHead>
2022-02-19 11:36:29 -08:00
<Th>{t('referrlals:link')}</Th>
<Th>{t('referrlals:copy-link')}</Th>
2022-02-10 15:48:14 -08:00
</TrHead>
</thead>
<tbody>
<TrBody>
<Td>
2022-02-10 17:50:40 -08:00
<div className="flex items-center">
{!isMobile ? (
<LinkIcon className="h-4 mr-1.5 w-4" />
) : null}
<p className="mb-0 text-th-fgd-1 max-w-md">
2022-02-10 17:50:40 -08:00
{isMobile
? abbreviateAddress(
mangoAccount.publicKey
)
: `https://trade.mango.markets?ref=${mangoAccount.publicKey.toString()}`}
2022-02-10 17:50:40 -08:00
</p>
</div>
2022-02-10 15:48:14 -08:00
</Td>
<Td className="flex items-center justify-end">
<IconButton
className={`flex-shrink-0 ${
hasCopied === 1 && 'bg-th-green'
}`}
disabled={hasCopied}
onClick={() =>
handleCopyLink(
`https://trade.mango.markets?ref=${mangoAccount.publicKey.toString()}`,
1
)
}
>
{hasCopied === 1 ? (
<CheckIcon className="h-5 w-5" />
) : (
<DuplicateIcon className="h-4 w-4" />
)}
</IconButton>
</Td>
</TrBody>
</tbody>
</Table>
) : (
<Table>
<thead>
<TrHead>
2022-02-19 11:36:29 -08:00
<Th>{t('referrals:link')}</Th>
2022-02-10 15:48:14 -08:00
<Th>
<div className="flex justify-end">
2022-02-19 11:36:29 -08:00
{t('referrals:copy-link')}
2022-02-10 15:48:14 -08:00
</div>
</Th>
</TrHead>
</thead>
<tbody>
{existingCustomRefLinks.map(
(customRefs, index) => (
<TrBody key={customRefs.referrerId}>
<Td>
<div className="flex items-center">
2022-02-10 17:50:40 -08:00
{!isMobile ? (
<LinkIcon className="h-4 mr-1.5 w-4" />
) : null}
2022-02-10 15:48:14 -08:00
<p className="mb-0 text-th-fgd-1">
2022-02-10 17:50:40 -08:00
{isMobile
? customRefs.referrerId
: `https://trade.mango.markets?ref=${customRefs.referrerId}`}
2022-02-10 15:48:14 -08:00
</p>
</div>
</Td>
<Td className="flex items-center justify-end">
<IconButton
className={`flex-shrink-0 ${
hasCopied === index + 1 &&
'bg-th-green'
}`}
disabled={hasCopied}
onClick={() =>
handleCopyLink(
`https://trade.mango.markets?ref=${customRefs.referrerId}`,
index + 1
)
}
>
{hasCopied === index + 1 ? (
<CheckIcon className="h-5 w-5" />
) : (
<DuplicateIcon className="h-4 w-4" />
)}
</IconButton>
</Td>
</TrBody>
)
)}
</tbody>
</Table>
)
) : (
<div className="space-y-2">
<div className="animate-pulse bg-th-bkg-4 h-16" />
<div className="animate-pulse bg-th-bkg-4 h-16" />
<div className="animate-pulse bg-th-bkg-4 h-16" />
</div>
)}
</div>
) : (
2022-02-10 17:50:40 -08:00
<div className="bg-th-bkg-3 flex flex-col flex-1 items-center justify-center px-4 py-8 rounded-md text-center">
2022-02-10 15:48:14 -08:00
<MngoMonoIcon className="h-6 mb-2 text-th-fgd-2 w-6" />
2022-02-19 11:36:29 -08:00
<p className="mb-0">{t('referrals:10k-mngo')}</p>
2022-02-10 15:48:14 -08:00
<Link href={'/?name=MNGO/USDC'} shallow={true}>
<a className="mt-4 px-6 py-2 bg-th-bkg-4 font-bold rounded-full text-th-fgd-1 hover:brightness-[1.15] hover:text-th-fgd-1 focus:outline-none">
2022-02-19 11:36:29 -08:00
{t('referrals:buy-mngo')}
2022-02-10 15:48:14 -08:00
</a>
</Link>
</div>
)}
2022-02-14 09:17:11 -08:00
</div>
{hasRequiredMngo ? (
<div className="min-w-[25%] bg-th-bkg-3 p-6 rounded-md w-full xl:w-1/3">
2022-02-19 11:36:29 -08:00
<h2 className="mb-1">{t('referrals:custom-links')}</h2>
2022-02-10 17:50:40 -08:00
<p className="mb-4">
2022-02-19 11:36:29 -08:00
{t('referrals:custom-links-limit')}
2022-02-10 17:50:40 -08:00
</p>
<div className="pb-6">
<label className="block mb-2 text-th-fgd-3 text-xs">
2022-02-19 11:36:29 -08:00
{t('referrals:referral-id')}
2022-02-10 17:50:40 -08:00
</label>
2022-02-10 18:29:07 -08:00
<Input
2022-02-10 17:50:40 -08:00
className="bg-th-bkg-1 border border-th-fgd-4 default-transition font-bold pl-4 h-12 focus:outline-none rounded-md text-base tracking-wide w-full hover:border-th-primary focus:border-th-primary"
2022-02-10 18:29:07 -08:00
error={!!inputError}
2022-02-10 17:50:40 -08:00
type="text"
placeholder="ElonMusk"
2022-02-10 18:29:07 -08:00
onBlur={validateRefIdInput}
onChange={(e) => onChangeRefIdInput(e.target.value)}
2022-02-10 17:50:40 -08:00
value={customRefLinkInput}
disabled={existingCustomRefLinks.length === 5}
2022-02-10 17:50:40 -08:00
/>
2022-02-10 18:29:07 -08:00
{inputError ? (
<div className="pt-2">
<InlineNotification
type="error"
desc={inputError}
/>
</div>
) : null}
2022-02-10 17:50:40 -08:00
</div>
<button
className="bg-th-primary flex items-center justify-center text-th-bkg-1 text-sm px-4 py-2 rounded-full hover:brightness-[1.15] focus:outline-none disabled:bg-th-bkg-4 disabled:text-th-fgd-4 disabled:cursor-not-allowed disabled:hover:brightness-100"
onClick={submitRefLink}
disabled={existingCustomRefLinks.length === 5}
2022-02-10 17:50:40 -08:00
>
<LinkIcon className="h-4 mr-1.5 w-4" />
2022-02-19 11:36:29 -08:00
{t('referrals:generate-link')}
2022-02-10 17:50:40 -08:00
</button>
</div>
) : null}
2022-02-10 15:48:14 -08:00
</div>
</div>
{referralHistory.length > 0 ? (
<div className="col-span-12">
2022-02-19 11:36:29 -08:00
<h2 className="mb-4">{t('referrals:earnings-history')}</h2>
2022-02-10 17:50:40 -08:00
{!isMobile ? (
<Table>
<thead>
<TrHead>
<Th>{t('date')}</Th>
2022-02-19 11:36:29 -08:00
<Th>{t('referrals:referral-id')}</Th>
<Th>{t('referrals:referee')}</Th>
2022-02-10 17:50:40 -08:00
<Th>
2022-02-19 11:36:29 -08:00
<div className="flex justify-end">
{t('referrals:fee-earned')}
</div>
2022-02-10 17:50:40 -08:00
</Th>
</TrHead>
</thead>
<tbody>
{referralHistory.map((ref, index) => (
<TrBody key={ref.fee + index}>
<Td>
{dayjs(ref.time).format('DD MMM YYYY h:mma')}
</Td>
<Td>{ref.referralLink}</Td>
<Td>
<Link
href={`/account?pubkey=${ref.referee}`}
shallow={true}
>
<a className="text-th-fgd-2 underline hover:no-underline hover:text-th-fgd-3">
{abbreviateAddress(mangoAccount.publicKey)}
</a>
</Link>
</Td>
<Td className="flex items-center justify-end">
${ref.fee}
</Td>
</TrBody>
))}
</tbody>
</Table>
) : (
<>
<MobileTableHeader
colOneHeader={t('date')}
2022-02-19 11:36:29 -08:00
colTwoHeader={t('referrals:fee-eanred')}
2022-02-10 17:50:40 -08:00
/>
2022-02-10 15:48:14 -08:00
{referralHistory.map((ref, index) => (
2022-02-10 17:50:40 -08:00
<ExpandableRow
buttonTemplate={
<div className="flex items-center justify-between text-th-fgd-1 w-full">
<div>
{dayjs(ref.time).format('DD MMM YYYY h:mma')}
</div>
<div className="text-right">${ref.fee}</div>
</div>
}
key={`${ref.fee + index}`}
index={index}
panelTemplate={
<>
<div className="grid grid-cols-2 grid-flow-row gap-4 pb-4">
<div className="text-left">
<div className="pb-0.5 text-th-fgd-3 text-xs">
2022-02-19 11:36:29 -08:00
{t('referrals:referral-id')}
2022-02-10 17:50:40 -08:00
</div>
<div>{ref.referralLink}</div>
</div>
<div className="text-left">
<div className="pb-0.5 text-th-fgd-3 text-xs">
2022-02-19 11:36:29 -08:00
{t('referrals:referee')}
2022-02-10 17:50:40 -08:00
</div>
<Link
href={`/account?pubkey=${ref.referee}`}
shallow={true}
>
<a className="text-th-fgd-2 underline hover:no-underline hover:text-th-fgd-3">
{abbreviateAddress(
mangoAccount.publicKey
)}
</a>
</Link>
</div>
</div>
</>
}
/>
2022-02-10 15:48:14 -08:00
))}
2022-02-10 17:50:40 -08:00
</>
)}
2022-02-10 15:48:14 -08:00
</div>
) : null}
</>
) : (
<>
<div className="col-span-12 lg:col-span-4 bg-th-bkg-3 p-6 rounded-md">
<ProgramDetails />
</div>
<div className="col-span-12 lg:col-span-8 bg-th-bkg-3 p-6 rounded-md flex items-center justify-center">
<EmptyState
buttonText={t('create-account')}
icon={<CurrencyDollarIcon />}
onClickButton={() => setShowAccountsModal(true)}
title={t('no-account-found')}
/>
</div>
</>
)
) : (
<>
<div className="col-span-12 lg:col-span-4 bg-th-bkg-3 p-6 rounded-md">
<ProgramDetails />
</div>
<div className="col-span-12 lg:col-span-8 bg-th-bkg-3 p-6 rounded-md flex items-center justify-center">
2022-02-10 15:48:14 -08:00
<EmptyState
buttonText={t('connect')}
icon={<LinkIcon />}
onClickButton={() => wallet.connect()}
title={t('connect-wallet')}
2022-02-10 15:48:14 -08:00
/>
</div>
</>
2022-02-10 15:48:14 -08:00
)}
2022-02-14 09:15:46 -08:00
</div>
2022-02-14 09:15:28 -08:00
</PageBodyContainer>
2022-02-10 15:48:14 -08:00
{showAccountsModal ? (
<AccountsModal
onClose={() => setShowAccountsModal(false)}
isOpen={showAccountsModal}
/>
) : null}
2022-02-14 09:15:28 -08:00
</div>
)
}