diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 162f685..6dc656d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -13,6 +13,7 @@ model Bounties { title String description String amount String + amount_sort Float issuer String in_progress Boolean? @default(true) is_joined_bounty Boolean? @default(false) diff --git a/src/app/[netname]/bounty/[id]/page.tsx b/src/app/[netname]/bounty/[id]/page.tsx index 9c063c3..dfc28b7 100644 --- a/src/app/[netname]/bounty/[id]/page.tsx +++ b/src/app/[netname]/bounty/[id]/page.tsx @@ -1,20 +1,50 @@ 'use client'; -import * as React from 'react'; -import { ToastContainer } from 'react-toastify'; - -import 'react-toastify/dist/ReactToastify.css'; - +import React, { useState } from 'react'; import BountyClaims from '@/components/bounty/BountyClaims'; import BountyInfo from '@/components/bounty/BountyInfo'; import CreateClaim from '@/components/ui/CreateClaim'; import NavBarMobile from '@/components/global/NavBarMobile'; import { useScreenSize } from '@/hooks/useScreenSize'; +import { useSearchParams } from 'next/navigation'; +import { useQuery } from '@tanstack/react-query'; +import { trpc, trpcClient } from '@/trpc/client'; +import { useGetChain } from '@/hooks/useGetChain'; +import Loading from '@/components/global/Loading'; export default function Bounty({ params }: { params: { id: string } }) { + const chain = useGetChain(); + const searchParams = useSearchParams(); const isMobile = useScreenSize(); + const utils = trpc.useUtils(); + const [status, setStatus] = useState('Indexing…'); + + const indexingMutation = useQuery({ + queryKey: ['indexing'], + queryFn: async () => { + setStatus('Indexing 1s'); + for (let i = 0; i < 60; i++) { + setStatus(`Indexing ${i}s`); + const bounty = await trpcClient.isBountyCreated.query({ + id: Number(params.id), + chainId: chain.id, + }); + + if (bounty) { + utils.bounty.invalidate(); + return; + } + + await new Promise((resolve) => setTimeout(resolve, 1_000)); + } + + throw new Error('Failed to index bounty'); + }, + enabled: searchParams.get('indexing') === 'true', + }); return ( <> +
@@ -24,7 +54,6 @@ export default function Bounty({ params }: { params: { id: string } }) { ) : ( )} -
); diff --git a/src/app/[netname]/page.tsx b/src/app/[netname]/page.tsx index acf62b6..01a5651 100644 --- a/src/app/[netname]/page.tsx +++ b/src/app/[netname]/page.tsx @@ -1,10 +1,6 @@ 'use client'; import React from 'react'; -import { ToastContainer } from 'react-toastify'; - -import 'react-toastify/dist/ReactToastify.css'; - import ContentHome from '@/components/layout/ContentHome'; import NavBarMobile from '@/components/global/NavBarMobile'; import CreateBounty from '@/components/ui/CreateBounty'; @@ -17,7 +13,6 @@ export default function Home() { <> {isMobile ? : } - ); } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index e61941f..a0940a1 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,11 +1,13 @@ import '@/styles/globals.css'; import '@/styles/colors.css'; +import 'react-toastify/dist/ReactToastify.css'; import { headers } from 'next/headers'; import React from 'react'; import { TRPCProvider } from '@/trpc/client'; import Header from '@/components/layout/Header'; import '@rainbow-me/rainbowkit/styles.css'; import { WalletProvider } from '@/components/global/WalletProvider'; +import { ToastContainer } from 'react-toastify'; export const metadata = { title: "poidh - pics or it didn't happen", @@ -28,6 +30,7 @@ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
{children} + diff --git a/src/app/page.tsx b/src/app/page.tsx index aa627a0..72c9a9c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,8 +3,6 @@ import { NetworkSelector } from '@/components/global/NetworkSelector'; import * as React from 'react'; -import 'react-toastify/dist/ReactToastify.css'; - const Home = () => { return (
diff --git a/src/components/bounty/BountyClaims.tsx b/src/components/bounty/BountyClaims.tsx index aaeb2f6..8e1540e 100644 --- a/src/components/bounty/BountyClaims.tsx +++ b/src/components/bounty/BountyClaims.tsx @@ -66,7 +66,7 @@ export default function BountyClaims({ bountyId }: { bountyId: string }) { ); if (!claims) { - return null; + return
No claims
; } return ( diff --git a/src/components/bounty/BountyInfo.tsx b/src/components/bounty/BountyInfo.tsx index b6a7530..194f66e 100644 --- a/src/components/bounty/BountyInfo.tsx +++ b/src/components/bounty/BountyInfo.tsx @@ -39,6 +39,12 @@ export default function BountyInfo({ bountyId }: { bountyId: string }) { const signMutation = useMutation({ mutationFn: async (bountyId: string) => { + //arbitrum has a problem with message signing, so all confirmations are on base + const chainId = await account.connector?.getChainId(); + if (chainId !== 8453) { + await switctChain.switchChainAsync({ chainId: 8453 }); + } + const message = getBanSignatureFirstLine({ id: Number(bountyId), diff --git a/src/components/bounty/ClaimItem.tsx b/src/components/bounty/ClaimItem.tsx index 8feb5d3..6b1a297 100644 --- a/src/components/bounty/ClaimItem.tsx +++ b/src/components/bounty/ClaimItem.tsx @@ -47,6 +47,11 @@ export default function ClaimItem({ const signMutation = useMutation({ mutationFn: async (claimId: string) => { + const chainId = await account.connector?.getChainId(); + if (chainId !== 8453) { + //arbitrum has a problem with message signing, so all confirmations are on base + await switctChain.switchChainAsync({ chainId: 8453 }); + } const message = getBanSignatureFirstLine({ id: Number(claimId), chainId: chain.id, diff --git a/src/components/global/FormBounty.tsx b/src/components/global/FormBounty.tsx index 099a178..e6a0666 100644 --- a/src/components/global/FormBounty.tsx +++ b/src/components/global/FormBounty.tsx @@ -16,7 +16,6 @@ import { decodeEventLog, parseEther } from 'viem'; import abi from '@/constant/abi/abi'; import { cn } from '@/utils'; import Loading from '@/components/global/Loading'; -import { trpcClient } from '@/trpc/client'; import GameButton from '@/components/global/GameButton'; import ButtonCTA from '@/components/ui/ButtonCTA'; import { InfoIcon } from '@/components/global/Icons'; @@ -83,25 +82,10 @@ export default function FormBounty({ throw new Error('Invalid event: ' + data.eventName); } - const bountyId = data.args.id.toString(); - - for (let i = 0; i < 60; i++) { - setStatus('Indexing ' + i + 's'); - const bounty = await trpcClient.isBountyCreated.query({ - id: Number(bountyId), - chainId: chain.id, - }); - - if (bounty) { - return bountyId; - } - await new Promise((resolve) => setTimeout(resolve, 1_000)); - } - - throw new Error('Failed to index bounty'); + return data.args.id.toString(); }, onSuccess: (bountyId) => { - router.push(`/${chain.slug}/bounty/${bountyId}`); + router.push(`/${chain.slug}/bounty/${bountyId}?indexing=true`); toast.success('Bounty created successfully'); }, onError: (error) => { diff --git a/src/components/global/Icons.tsx b/src/components/global/Icons.tsx index adc7c59..a06fd88 100644 --- a/src/components/global/Icons.tsx +++ b/src/components/global/Icons.tsx @@ -351,3 +351,29 @@ export function CloseIcon({ ); } + +export function SortIcon({ + width = 24, + height = 24, +}: { + width?: number; + height?: number; +}) { + return ( + + + + ); +} diff --git a/src/components/layout/ContentHome.tsx b/src/components/layout/ContentHome.tsx index 585e848..61c4642 100644 --- a/src/components/layout/ContentHome.tsx +++ b/src/components/layout/ContentHome.tsx @@ -6,11 +6,15 @@ import { useGetChain } from '@/hooks/useGetChain'; import { trpc } from '@/trpc/client'; import BountyList from '@/components/ui/BountyList'; import { cn } from '@/utils'; +import { FormControl, MenuItem, Select } from '@mui/material'; +import { SortIcon } from '@/components/global/Icons'; type DisplayType = 'open' | 'progress' | 'past'; +type SortType = 'value' | 'id'; export default function ContentHome() { const [display, setDisplay] = useState('open'); + const [sortType, setSortType] = useState('id'); const chain = useGetChain(); const bounties = trpc.bounties.useInfiniteQuery( @@ -18,6 +22,7 @@ export default function ContentHome() { chainId: chain.id, status: display, limit: 6, + sortType, }, { getNextPageParam: (lastPage) => lastPage.nextCursor, @@ -26,36 +31,90 @@ export default function ContentHome() { return ( <> -
-
- - - + + + +
+
+
+ + +
diff --git a/src/trpc/routers/_app.ts b/src/trpc/routers/_app.ts index 9bee49f..a1161ed 100644 --- a/src/trpc/routers/_app.ts +++ b/src/trpc/routers/_app.ts @@ -71,10 +71,19 @@ export const appRouter = createTRPCRouter({ chainId: z.number(), status: z.enum(['open', 'progress', 'past']), limit: z.number().min(1).max(100).default(10), - cursor: z.number().nullish(), + cursor: z + .object({ + id: z.number(), + amount_sort: z.number(), + ids: z.array(z.number()), + }) + .nullish(), + sortType: z.enum(['value', 'id']).default('id'), }) ) .query(async ({ input }) => { + const sortById = input.sortType === 'id'; + const sortByValue = input.sortType === 'value'; const items = await prisma.bounties.findMany({ where: { chain_id: input.chainId, @@ -99,20 +108,40 @@ export const appRouter = createTRPCRouter({ is_canceled: false, } : {}), - ...(input.cursor ? { id: { lt: input.cursor } } : {}), + ...(input.cursor + ? sortById + ? { id: { lt: input.cursor.id } } + : { amount_sort: { lte: input.cursor.amount_sort } } + : {}), + ...(input.cursor && !sortById && { id: { notIn: input.cursor.ids } }), }, include: { claims: { take: 1, }, }, - orderBy: { id: 'desc' }, + orderBy: sortById + ? { id: 'desc' } + : sortByValue + ? { amount_sort: 'desc' } + : {}, take: input.limit, }); - let nextCursor: (typeof items)[number]['id'] | undefined = undefined; + let nextCursor: + | { + id: (typeof items)[number]['id']; + amount_sort: (typeof items)[number]['amount_sort']; + ids: (typeof items)[number]['id'][]; + } + | undefined = undefined; + if (items.length === input.limit) { - nextCursor = items[items.length - 1].id; + nextCursor = { + id: items[items.length - 1].id, + amount_sort: items[items.length - 1].amount_sort, + ids: [...(input.cursor?.ids ?? []), ...items.map((item) => item.id)], + }; } return { @@ -527,7 +556,7 @@ export const appRouter = createTRPCRouter({ } const isAdmin = checkIsAdmin(input.address); - const chain = chains[input.chainName]; + const chain = chains['base']; if (!isAdmin) { throw new TRPCError({