Skip to content

Commit

Permalink
Merge pull request #127 from labscommunity/kranthi/bounties-usd
Browse files Browse the repository at this point in the history
feat: add amount base change to bounty
  • Loading branch information
kranthicodes authored Jun 18, 2024
2 parents 7ac8026 + 872ed59 commit 457d4c7
Show file tree
Hide file tree
Showing 11 changed files with 172 additions and 28 deletions.
14 changes: 9 additions & 5 deletions src/pages/issue/read/tabs/bounty/BountyRow.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { differenceInDays, formatDistanceToNow } from 'date-fns'
import { differenceInDays, formatDistanceToNow, startOfToday } from 'date-fns'
import { TbMoneybag } from 'react-icons/tb'
import SVG from 'react-inlinesvg'

import ArweaveLogo from '@/assets/arweave.svg'
import { resolveUsernameOrShorten } from '@/helpers/resolveUsername'
import { BountyStatus } from '@/types/repository'
import { BountyBase, BountyStatus } from '@/types/repository'

type Props = {
status: BountyStatus
Expand All @@ -13,6 +13,7 @@ type Props = {
author: string
timestamp: number
expiry: number
base: BountyBase
onClick: () => void
}

Expand All @@ -30,10 +31,11 @@ const STATUS_TO_TEXT = {
CLAIMED: 'been completed'
}

export default function BountyRow({ status, author, id, amount, expiry, timestamp, onClick }: Props) {
export default function BountyRow({ status, author, id, amount, expiry, timestamp, onClick, base }: Props) {
const Icon = STATUS_TO_ICON_MAP[status]

const isActive = status === 'ACTIVE'

return (
<div
onClick={onClick}
Expand All @@ -45,7 +47,9 @@ export default function BountyRow({ status, author, id, amount, expiry, timestam
src={ArweaveLogo}
className="w-5 h-5 [&>circle]:stroke-gray-900 [&>circle]:stroke-[2.5] [&>circle]:fill-none [&>path]:fill-gray-900"
/>
<span className="font-medium text-lg">{amount.toFixed(2)} AR</span>
<span className="font-medium text-lg">
{amount.toFixed(2)} {base}
</span>
</div>
<div className="flex gap-3 text-gray-900">
<span className="font-semibold">Reward#{id}</span>
Expand All @@ -54,7 +58,7 @@ export default function BountyRow({ status, author, id, amount, expiry, timestam
{expiry && isActive && (
<>
and expires in
<span className="font-medium">{differenceInDays(new Date(expiry * 1000), new Date())} Days</span>
<span className="font-medium">{differenceInDays(new Date(expiry * 1000), startOfToday())} Days</span>
</>
)}
{expiry && !isActive && <>and has {STATUS_TO_TEXT[status]}!</>}
Expand Down
41 changes: 39 additions & 2 deletions src/pages/issue/read/tabs/bounty/NewBountyModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import ArweaveLogo from '@/assets/arweave.svg'
import CloseCrossIcon from '@/assets/icons/close-cross.svg'
import { Button } from '@/components/common/buttons'
import { useGlobalStore } from '@/stores/globalStore'
import { BountyBase } from '@/types/repository'

type NewBountyModalProps = {
setIsOpen: (val: boolean) => void
Expand All @@ -33,6 +34,7 @@ const schema = yup
.required()

export default function NewBountyModal({ isOpen, setIsOpen }: NewBountyModalProps) {
const [base, setBase] = React.useState<BountyBase>('AR')
const { issueId } = useParams()
const [addBounty] = useGlobalStore((state) => [state.issuesActions.addBounty])
const [isSubmitting, setIsSubmitting] = React.useState(false)
Expand All @@ -49,7 +51,7 @@ export default function NewBountyModal({ isOpen, setIsOpen }: NewBountyModalProp

const unixTimestampOfExpiry = Math.floor(data.expiry.getTime() / 1000)

await addBounty(+issueId!, data.amount, unixTimestampOfExpiry)
await addBounty(+issueId!, data.amount, unixTimestampOfExpiry, base)

setIsSubmitting(false)

Expand All @@ -60,6 +62,10 @@ export default function NewBountyModal({ isOpen, setIsOpen }: NewBountyModalProp
setIsOpen(false)
}

function handleBaseChange(newBase: BountyBase) {
setBase(newBase)
}

return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
Expand Down Expand Up @@ -106,6 +112,37 @@ export default function NewBountyModal({ isOpen, setIsOpen }: NewBountyModalProp
<span>Arweave</span>
</div>
</div>
<div className="mt-2">
<label htmlFor="amount" className="block mb-1 text-sm font-medium text-gray-600">
Base
</label>
<div className="flex items-center p-1 bg-gray-100 border-[1px] border-gray-300 rounded-lg gap-1 h-10 order-1">
<div
onClick={() => handleBaseChange('AR')}
className={clsx(
'cursor-pointer text-gray-700 w-1/2 h-full flex items-center justify-center font-semibold',
{
'px-2': base !== 'AR',
'px-3 bg-primary-600 text-white rounded-md': base === 'AR'
}
)}
>
AR
</div>
<div
onClick={() => handleBaseChange('USD')}
className={clsx(
'cursor-pointer text-gray-700 w-1/2 h-full flex items-center justify-center font-semibold',
{
'px-2': base === 'AR',
'px-3 bg-primary-600 text-white rounded-md': base !== 'AR'
}
)}
>
USD
</div>
</div>
</div>
<div className="mt-2">
<label htmlFor="amount" className="block mb-1 text-sm font-medium text-gray-600">
Amount
Expand All @@ -123,7 +160,7 @@ export default function NewBountyModal({ isOpen, setIsOpen }: NewBountyModalProp
min={'0'}
/>
<div className="h-full absolute right-4 top-0 flex items-center">
<span className="font-medium text-gray-600">AR</span>
<span className="font-medium text-gray-600">{base}</span>
</div>
</div>
{errors.amount && <p className="text-red-500 text-sm italic mt-2">{errors.amount.message}</p>}
Expand Down
101 changes: 94 additions & 7 deletions src/pages/issue/read/tabs/bounty/ReadBountyModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Dialog, Transition } from '@headlessui/react'
import Arweave from 'arweave/web'
import clsx from 'clsx'
import { differenceInDays } from 'date-fns'
import { differenceInDays, startOfToday } from 'date-fns'
import React, { Fragment } from 'react'
import toast from 'react-hot-toast'
import SVG from 'react-inlinesvg'
Expand All @@ -10,8 +10,10 @@ import { useParams } from 'react-router-dom'
import ArweaveLogo from '@/assets/arweave.svg'
import CloseCrossIcon from '@/assets/icons/close-cross.svg'
import { Button } from '@/components/common/buttons'
import { getArweaveUSD } from '@/helpers/prices'
import { useGlobalStore } from '@/stores/globalStore'
import { Bounty } from '@/types/repository'
import { Bounty, BountyBase } from '@/types/repository'

type NewBountyModalProps = {
setIsOpen: (val: boolean) => void
isOpen: boolean
Expand All @@ -33,6 +35,9 @@ const arweave = new Arweave({
})

export default function ReadBountyModal({ isOpen, setIsOpen, bounty, author }: NewBountyModalProps) {
const [amount, setAmount] = React.useState(bounty.amount)
const [oppositePrice, setOppositePrice] = React.useState<null | number>(null)
const [base, setBase] = React.useState<BountyBase>(bounty.base)
const [bountyComplete, setBountyComplete] = React.useState(false)
const [payTxId, setPayTxId] = React.useState('')
const { issueId } = useParams()
Expand All @@ -43,6 +48,42 @@ export default function ReadBountyModal({ isOpen, setIsOpen, bounty, author }: N
])
const [isSubmitting, setIsSubmitting] = React.useState(false)

React.useEffect(() => {
if (bounty.base === 'AR') {
//fetch usd
fetchOppositePrice('AR')
}

if (bounty.base === 'USD') {
fetchOppositePrice('USD')
}
}, [])

React.useEffect(() => {
if (base !== bounty.base && oppositePrice) {
setAmount(oppositePrice)
}

if (base === bounty.base) {
setAmount(bounty.amount)
}
}, [base])

async function fetchOppositePrice(side: string) {
const arUSD = await getArweaveUSD()

if (side === 'AR') {
//set USD
setOppositePrice(arUSD * bounty.amount)
}

if (side === 'USD') {
//set AR
const usdAR = (bounty.amount / arUSD).toPrecision(3)
setOppositePrice(+usdAR)
}
}

async function handleCloseButtonClick() {
setIsSubmitting(true)
if (bountyComplete && payTxId.length > 0) {
Expand All @@ -59,7 +100,16 @@ export default function ReadBountyModal({ isOpen, setIsOpen, bounty, author }: N
return
}

const bountyAmountWinston = arweave.ar.arToWinston(bounty.amount.toString())
const queryAmount = bounty.base === 'USD' ? oppositePrice : bounty.amount

if (!queryAmount) {
toast.error('Something went wrong. Try again.')
setIsSubmitting(false)

return
}

const bountyAmountWinston = arweave.ar.arToWinston(queryAmount.toString())

if (data.quantity !== bountyAmountWinston) {
toast.error('Incorrect amount was sent in this transaction. Please provide correct transaction hash.')
Expand Down Expand Up @@ -89,6 +139,10 @@ export default function ReadBountyModal({ isOpen, setIsOpen, bounty, author }: N
setIsOpen(false)
}

function handleBaseChange(newBase: BountyBase) {
setBase(newBase)
}

return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
Expand Down Expand Up @@ -135,6 +189,37 @@ export default function ReadBountyModal({ isOpen, setIsOpen, bounty, author }: N
<span>Arweave</span>
</div>
</div>
<div className="mt-2">
<label htmlFor="amount" className="block mb-1 text-sm font-medium text-gray-600">
Base
</label>
<div className="flex items-center p-1 bg-gray-100 border-[1px] border-gray-300 rounded-lg gap-1 h-10 order-1">
<div
onClick={() => handleBaseChange('AR')}
className={clsx(
'cursor-pointer text-gray-700 w-1/2 h-full flex items-center justify-center font-semibold',
{
'px-2': base !== 'AR',
'px-3 bg-primary-600 text-white rounded-md': base === 'AR'
}
)}
>
AR
</div>
<div
onClick={() => handleBaseChange('USD')}
className={clsx(
'cursor-pointer text-gray-700 w-1/2 h-full flex items-center justify-center font-semibold',
{
'px-2': base === 'AR',
'px-3 bg-primary-600 text-white rounded-md': base !== 'AR'
}
)}
>
USD
</div>
</div>
</div>
<div>
<label htmlFor="amount" className="block mb-1 text-sm font-medium text-gray-600">
Amount
Expand All @@ -145,12 +230,12 @@ export default function ReadBountyModal({ isOpen, setIsOpen, bounty, author }: N
'bg-white border-[1px] text-gray-900 text-base rounded-lg hover:shadow-[0px_2px_4px_0px_rgba(0,0,0,0.10)] focus:border-primary-500 focus:border-[1.5px] block w-full px-3 py-[10px] outline-none',
'border-gray-300'
)}
value={bounty.amount}
value={amount}
type="number"
disabled
/>
<div className="h-full absolute right-4 top-0 flex items-center">
<span className="font-medium text-gray-900">AR</span>
<span className="font-medium text-gray-900">{base}</span>
</div>
</div>
</div>
Expand All @@ -161,7 +246,7 @@ export default function ReadBountyModal({ isOpen, setIsOpen, bounty, author }: N
</label>

<div className="font-medium text-gray-900">
{differenceInDays(new Date(bounty.expiry * 1000), new Date())} Days
{differenceInDays(new Date(bounty.expiry * 1000), startOfToday())} Days
</div>
</div>
)}
Expand Down Expand Up @@ -216,7 +301,9 @@ export default function ReadBountyModal({ isOpen, setIsOpen, bounty, author }: N
<div className="py-2">
<span>Transaction: </span>
<span className="font-medium text-primary-700 underline">
<a target='_blank' href={`https://viewblock.io/arweave/tx/${bounty.paymentTxId}`}>View Block</a>
<a target="_blank" href={`https://viewblock.io/arweave/tx/${bounty.paymentTxId}`}>
View Block
</a>
</span>
</div>
)}
Expand Down
8 changes: 7 additions & 1 deletion src/pages/issue/read/tabs/bounty/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ export default function BountyTab() {
setViewBountyOpen(true)
}

function handleClose() {
setSelectedBounty(null)
setViewBountyOpen(false)
}

const hasBounties = bountiesList.length > 0

return (
Expand All @@ -111,6 +116,7 @@ export default function BountyTab() {
timestamp={bounty.timestamp}
expiry={bounty.expiry}
onClick={() => handleRowClick(bounty)}
base={bounty.base}
/>
))}
</div>
Expand All @@ -120,7 +126,7 @@ export default function BountyTab() {
author={selectedIssue?.author || ''}
bounty={selectedBounty}
isOpen={isViewBountyOpen}
setIsOpen={setViewBountyOpen}
setIsOpen={handleClose}
/>
)}
</div>
Expand Down
9 changes: 3 additions & 6 deletions src/pages/repository/components/RepoHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import IconStarOutline from '@/assets/icons/star-outline.svg'
import { Button } from '@/components/common/buttons'
import { trackGoogleAnalyticsPageView } from '@/helpers/google-analytics'
import { resolveUsernameOrShorten } from '@/helpers/resolveUsername'
import { useGlobalStore } from '@/stores/globalStore'
import { Repo } from '@/types/repository'

import useRepository from '../hooks/useRepository'
Expand All @@ -30,7 +29,6 @@ type Props = {
}

export default function RepoHeader({ repo, isLoading, owner, parentRepo }: Props) {
const getUserFromAddress = useGlobalStore((state) => state.userActions.getUserFromAddress)
const [isForkModalOpen, setIsForkModalOpen] = React.useState(false)
const [showCloneDropdown, setShowCloneDropdown] = React.useState(false)
const cloneRef = React.useRef<HTMLDivElement | null>(null)
Expand Down Expand Up @@ -92,8 +90,6 @@ export default function RepoHeader({ repo, isLoading, owner, parentRepo }: Props
navigate(`/repository/${parentRepo.id}`)
}

const repoOwner = getUserFromAddress(repo?.owner)

return (
<div className="flex flex-col">
<div className="flex justify-between">
Expand Down Expand Up @@ -180,9 +176,10 @@ export default function RepoHeader({ repo, isLoading, owner, parentRepo }: Props
<div className="flex w-full px-2 py-1 gap-1 justify-between items-center border-[0.5px] border-gray-300 bg-gray-200 rounded-md overflow-hidden">
<div className="pr-2 overflow-scroll [&::-webkit-scrollbar]:hidden whitespace-nowrap">
<div ref={cloneRef} className="text-gray-900 w-full flex">
{repoOwner?.username
{/* {repoOwner?.username
? `git clone proland://${repoOwner.username}/${repo.name}`
: `git clone proland://${repo.id} ${repo.name}`}
: `git clone proland://${repo.id} ${repo.name}`} */}
git clone proland://{repo.id} {repo.name}
</div>
</div>
<div onClick={handleCopyClone} className="text-gray-900 bg-gray-200 h-full px-1 cursor-pointer">
Expand Down
Loading

0 comments on commit 457d4c7

Please sign in to comment.