diff --git a/src/pages/issue/read/tabs/bounty/BountyRow.tsx b/src/pages/issue/read/tabs/bounty/BountyRow.tsx index 3ecd3e02..9db0a22a 100644 --- a/src/pages/issue/read/tabs/bounty/BountyRow.tsx +++ b/src/pages/issue/read/tabs/bounty/BountyRow.tsx @@ -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 @@ -13,6 +13,7 @@ type Props = { author: string timestamp: number expiry: number + base: BountyBase onClick: () => void } @@ -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 (
- {amount.toFixed(2)} AR + + {amount.toFixed(2)} {base} +
Reward#{id} @@ -54,7 +58,7 @@ export default function BountyRow({ status, author, id, amount, expiry, timestam {expiry && isActive && ( <> and expires in - {differenceInDays(new Date(expiry * 1000), new Date())} Days + {differenceInDays(new Date(expiry * 1000), startOfToday())} Days )} {expiry && !isActive && <>and has {STATUS_TO_TEXT[status]}!} diff --git a/src/pages/issue/read/tabs/bounty/NewBountyModal.tsx b/src/pages/issue/read/tabs/bounty/NewBountyModal.tsx index f1277385..35792602 100644 --- a/src/pages/issue/read/tabs/bounty/NewBountyModal.tsx +++ b/src/pages/issue/read/tabs/bounty/NewBountyModal.tsx @@ -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 @@ -33,6 +34,7 @@ const schema = yup .required() export default function NewBountyModal({ isOpen, setIsOpen }: NewBountyModalProps) { + const [base, setBase] = React.useState('AR') const { issueId } = useParams() const [addBounty] = useGlobalStore((state) => [state.issuesActions.addBounty]) const [isSubmitting, setIsSubmitting] = React.useState(false) @@ -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) @@ -60,6 +62,10 @@ export default function NewBountyModal({ isOpen, setIsOpen }: NewBountyModalProp setIsOpen(false) } + function handleBaseChange(newBase: BountyBase) { + setBase(newBase) + } + return ( @@ -106,6 +112,37 @@ export default function NewBountyModal({ isOpen, setIsOpen }: NewBountyModalProp Arweave
+
+ +
+
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 +
+
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 +
+
+
{errors.amount &&

{errors.amount.message}

} diff --git a/src/pages/issue/read/tabs/bounty/ReadBountyModal.tsx b/src/pages/issue/read/tabs/bounty/ReadBountyModal.tsx index 3db0c0a4..a7699751 100644 --- a/src/pages/issue/read/tabs/bounty/ReadBountyModal.tsx +++ b/src/pages/issue/read/tabs/bounty/ReadBountyModal.tsx @@ -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' @@ -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 @@ -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) + const [base, setBase] = React.useState(bounty.base) const [bountyComplete, setBountyComplete] = React.useState(false) const [payTxId, setPayTxId] = React.useState('') const { issueId } = useParams() @@ -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) { @@ -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.') @@ -89,6 +139,10 @@ export default function ReadBountyModal({ isOpen, setIsOpen, bounty, author }: N setIsOpen(false) } + function handleBaseChange(newBase: BountyBase) { + setBase(newBase) + } + return ( @@ -135,6 +189,37 @@ export default function ReadBountyModal({ isOpen, setIsOpen, bounty, author }: N Arweave +
+ +
+
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 +
+
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 +
+
+
@@ -161,7 +246,7 @@ export default function ReadBountyModal({ isOpen, setIsOpen, bounty, author }: N
- {differenceInDays(new Date(bounty.expiry * 1000), new Date())} Days + {differenceInDays(new Date(bounty.expiry * 1000), startOfToday())} Days
)} @@ -216,7 +301,9 @@ export default function ReadBountyModal({ isOpen, setIsOpen, bounty, author }: N
Transaction: - View Block + + View Block +
)} diff --git a/src/pages/issue/read/tabs/bounty/index.tsx b/src/pages/issue/read/tabs/bounty/index.tsx index dd987a9b..4ff62e10 100644 --- a/src/pages/issue/read/tabs/bounty/index.tsx +++ b/src/pages/issue/read/tabs/bounty/index.tsx @@ -89,6 +89,11 @@ export default function BountyTab() { setViewBountyOpen(true) } + function handleClose() { + setSelectedBounty(null) + setViewBountyOpen(false) + } + const hasBounties = bountiesList.length > 0 return ( @@ -111,6 +116,7 @@ export default function BountyTab() { timestamp={bounty.timestamp} expiry={bounty.expiry} onClick={() => handleRowClick(bounty)} + base={bounty.base} /> ))} @@ -120,7 +126,7 @@ export default function BountyTab() { author={selectedIssue?.author || ''} bounty={selectedBounty} isOpen={isViewBountyOpen} - setIsOpen={setViewBountyOpen} + setIsOpen={handleClose} /> )} diff --git a/src/pages/repository/components/RepoHeader.tsx b/src/pages/repository/components/RepoHeader.tsx index 862ec954..e7131212 100644 --- a/src/pages/repository/components/RepoHeader.tsx +++ b/src/pages/repository/components/RepoHeader.tsx @@ -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' @@ -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(null) @@ -92,8 +90,6 @@ export default function RepoHeader({ repo, isLoading, owner, parentRepo }: Props navigate(`/repository/${parentRepo.id}`) } - const repoOwner = getUserFromAddress(repo?.owner) - return (
@@ -180,9 +176,10 @@ export default function RepoHeader({ repo, isLoading, owner, parentRepo }: Props
- {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}
diff --git a/src/stores/issues/actions/index.ts b/src/stores/issues/actions/index.ts index 6c582743..5ecf1902 100644 --- a/src/stores/issues/actions/index.ts +++ b/src/stores/issues/actions/index.ts @@ -3,7 +3,7 @@ import getWarpContract from '@/helpers/getWrapContract' import { isInvalidInput } from '@/helpers/isInvalidInput' import { getSigner } from '@/helpers/wallet/getSigner' import { postIssueStatDataTxToArweave } from '@/lib/user' -import { Issue } from '@/types/repository' +import { BountyBase, Issue } from '@/types/repository' async function getContract() { const userSigner = await getSigner() @@ -182,7 +182,7 @@ export async function updateIssueDetails(repoId: string, issueId: number, issue: }) } -export async function addBounty(repoId: string, issueId: number, amount: number, expiry: number) { +export async function addBounty(repoId: string, issueId: number, amount: number, expiry: number, base: BountyBase) { const contract = await getContract() await contract.writeInteraction({ @@ -191,7 +191,8 @@ export async function addBounty(repoId: string, issueId: number, amount: number, repoId, issueId, amount, - expiry + expiry, + base } }) diff --git a/src/stores/issues/index.ts b/src/stores/issues/index.ts index 91ed60ec..f8ea9da0 100644 --- a/src/stores/issues/index.ts +++ b/src/stores/issues/index.ts @@ -337,7 +337,7 @@ const createPullRequestSlice: StateCreator { + addBounty: async (id, amount, expiry, base) => { const repo = get().repoCoreState.selectedRepo.repo if (!repo) { @@ -346,7 +346,7 @@ const createPullRequestSlice: StateCreator addBounty(repo.id, id, amount, expiry)) + const { error, response } = await withAsync(() => addBounty(repo.id, id, amount, expiry, base)) if (!error && response) { const bounties = response?.bounties @@ -362,6 +362,7 @@ const createPullRequestSlice: StateCreator Promise addComment: (id: number, comment: string) => Promise updateComment: (id: number, comment: { id: number; description: string }) => Promise - addBounty: (id: number, amount: number, expiry: number) => Promise + addBounty: (id: number, amount: number, expiry: number, base: BountyBase) => Promise closeBounty: (issueId: number, bountyId: number) => Promise expireBounty: (issueId: number, bountyId: number) => Promise completeBounty: (issueId: number, bountyId: number, paymentTxId: string) => Promise diff --git a/src/types/repository.ts b/src/types/repository.ts index afdce3cc..7b148b3c 100644 --- a/src/types/repository.ts +++ b/src/types/repository.ts @@ -123,7 +123,9 @@ export type Bounty = { status: BountyStatus paymentTxId: string | null timestamp: number + base: BountyBase } +export type BountyBase = 'USD' | 'AR' export type IssueActivity = IssueActivityStatus | IssueActivityComment diff --git a/warp/protocol-land/actions/issues.ts b/warp/protocol-land/actions/issues.ts index 0063796f..435aad82 100644 --- a/warp/protocol-land/actions/issues.ts +++ b/warp/protocol-land/actions/issues.ts @@ -387,10 +387,15 @@ export async function createNewBounty( throw new ContractError('Error: You dont have permissions for this operation.') } + if (!payload.base) { + payload.base = 'AR' + } + const bounty: Bounty = { id: 1, amount: payload.amount, expiry: payload.expiry, + base: payload.base, paymentTxId: null, status: 'ACTIVE', timestamp: getBlockTimeStamp() diff --git a/warp/protocol-land/types/index.ts b/warp/protocol-land/types/index.ts index f90e1e40..d43c7c73 100644 --- a/warp/protocol-land/types/index.ts +++ b/warp/protocol-land/types/index.ts @@ -141,12 +141,15 @@ export type Issue = { export type Bounty = { id: number amount: number + base: BountyBase expiry: number status: BountyStatus paymentTxId: string | null timestamp: number } +export type BountyBase = 'USD' | 'AR' + export type IssueActivity = IssueActivityStatus | IssueActivityComment export type PullRequestActivity = PullRequestActivityStatus | PullRequestActivityComment