diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1f3eb359..162f6852 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -17,7 +17,6 @@ model Bounties { in_progress Boolean? @default(true) is_joined_bounty Boolean? @default(false) is_canceled Boolean? @default(false) - is_banned Boolean? @default(false) is_multiplayer Boolean? is_voting Boolean? @default(false) deadline Int? @@ -25,6 +24,7 @@ model Bounties { claims Claims[] participations ParticipationsBounties[] issuerUser Users? @relation(fields: [issuer], references: [address]) + ban Ban[] @@id([id, chain_id]) } @@ -36,13 +36,13 @@ model Claims { description String url String issuer String - is_banned Boolean? is_accepted Boolean? bounty_id Int owner String bounty Bounties? @relation(fields: [bounty_id, chain_id], references: [id, chain_id]) issuerUser Users? @relation(fields: [issuer], references: [address]) + ban Ban[] @@id([id, chain_id]) } @@ -70,3 +70,15 @@ model Users { @@id([address]) } + +model Ban { + id Int @id @default(autoincrement()) + chain_id Int + bounty_id Int? + claim_id Int? + banned_at DateTime @default(now()) + banned_by String + + bounty Bounties? @relation(fields: [bounty_id, chain_id], references: [id, chain_id]) + claim Claims? @relation(fields: [claim_id, chain_id], references: [id, chain_id]) +} diff --git a/src/app/[netname]/bounty/[id]/page.tsx b/src/app/[netname]/bounty/[id]/page.tsx index 01f37cbb..9c063c3b 100644 --- a/src/app/[netname]/bounty/[id]/page.tsx +++ b/src/app/[netname]/bounty/[id]/page.tsx @@ -7,15 +7,23 @@ import 'react-toastify/dist/ReactToastify.css'; 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'; export default function Bounty({ params }: { params: { id: string } }) { + const isMobile = useScreenSize(); + return ( <>
- + {isMobile ? ( + + ) : ( + + )}
diff --git a/src/app/[netname]/layout.tsx b/src/app/[netname]/layout.tsx index 3d29f9ad..5a3af1bc 100644 --- a/src/app/[netname]/layout.tsx +++ b/src/app/[netname]/layout.tsx @@ -1,7 +1,6 @@ import { Metadata } from 'next'; import React from 'react'; -import Wrapper from '@/components/global/Wrapper'; import { Netname } from '@/utils/types'; type Props = { @@ -26,5 +25,5 @@ export async function generateMetadata({ params }: Props): Promise { } export default function Layout({ children }: Props) { - return {children}; + return children; } diff --git a/src/app/[netname]/page.tsx b/src/app/[netname]/page.tsx index f486c41c..acf62b64 100644 --- a/src/app/[netname]/page.tsx +++ b/src/app/[netname]/page.tsx @@ -6,13 +6,17 @@ 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'; +import { useScreenSize } from '@/hooks/useScreenSize'; export default function Home() { + const isMobile = useScreenSize(); + return ( <> - + {isMobile ? : } ); diff --git a/src/components/bounty/BountyInfo.tsx b/src/components/bounty/BountyInfo.tsx index b83955c3..9d78d1bb 100644 --- a/src/components/bounty/BountyInfo.tsx +++ b/src/components/bounty/BountyInfo.tsx @@ -142,15 +142,15 @@ export default function BountyInfo({ bountyId }: { bountyId: string }) { toast.error('You are not an admin'); } }} - disabled={bounty.data.is_banned || false} + disabled={bounty.data.ban.length > 0 || false} className={cn( 'border border-[#F15E5F] w-fit rounded-md py-2 px-5 mt-5', - bounty.data.isBanned + bounty.data.ban.length > 0 ? 'bg-red-400 text-white' : 'hover:bg-red-400 hover:text-white' )} > - {bounty.data.isBanned ? 'banned' : 'ban'} + {bounty.data.ban.length > 0 ? 'banned' : 'ban'} )}

diff --git a/src/components/bounty/ClaimItem.tsx b/src/components/bounty/ClaimItem.tsx index 0ce7b9f8..8feb5d37 100644 --- a/src/components/bounty/ClaimItem.tsx +++ b/src/components/bounty/ClaimItem.tsx @@ -57,6 +57,7 @@ export default function ClaimItem({ if (!signature) { throw new Error('Failed to sign message'); } + await banClaimMutation.mutateAsync({ id: Number(claimId), chainId: chain.id, diff --git a/src/components/global/FormClaim.tsx b/src/components/global/FormClaim.tsx index 3ad2243d..fc021ac6 100644 --- a/src/components/global/FormClaim.tsx +++ b/src/components/global/FormClaim.tsx @@ -35,6 +35,7 @@ export default function FormClaim({ const [status, setStatus] = useState(''); const [file, setFile] = useState(null); const utils = trpc.useUtils(); + const [uploading, setUploading] = useState(false); const account = useAccount(); const writeContract = useWriteContract({}); @@ -53,15 +54,7 @@ export default function FormClaim({ reader.readAsDataURL(file); }, []); - const { getRootProps, getInputProps, isDragActive } = useDropzone({ - onDrop, - maxFiles: 1, - accept: { - 'image/png': ['.png'], - 'image/jpeg': ['.jpg', '.jpeg'], - 'image/heic': ['.heic'], - }, - }); + const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop }); const compressImage = async (image: File): Promise => { const options = { @@ -102,15 +95,19 @@ export default function FormClaim({ useEffect(() => { const uploadImage = async () => { if (file) { + setUploading(true); try { const compressedFile = await compressImage(file); const cid = await retryUpload(compressedFile); setImageURI(`${LINK_IPFS}/${cid}`); } catch (error) { - toast.error('Failed to upload image: ' + error); + console.error('Error uploading file:', error); + alert('Trouble uploading file'); } + setUploading(false); } }; + uploadImage(); }, [file]); @@ -208,20 +205,7 @@ export default function FormClaim({

{isDragActive ? ( @@ -237,13 +221,7 @@ export default function FormClaim({ Preview )}
@@ -271,7 +249,7 @@ export default function FormClaim({ account.isDisconnected && 'opacity-50 cursor-not-allowed' )} onClick={() => { - if (name && description && imageURI) { + if (name && description && imageURI && !uploading) { onClose(); createClaimMutations.mutate(BigInt(bountyId)); } else { diff --git a/src/components/global/GameButton.tsx b/src/components/global/GameButton.tsx index 968e0768..3b1c0de1 100644 --- a/src/components/global/GameButton.tsx +++ b/src/components/global/GameButton.tsx @@ -1,10 +1,10 @@ export default function GameButton() { return ( - <> +
- +
+ ); +} + +export function PlainGameButton() { + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
); } diff --git a/src/components/global/NavBarMobile.tsx b/src/components/global/NavBarMobile.tsx new file mode 100644 index 00000000..572d0c44 --- /dev/null +++ b/src/components/global/NavBarMobile.tsx @@ -0,0 +1,70 @@ +import FormBounty from '@/components/global/FormBounty'; +import FormClaim from '@/components/global/FormClaim'; +import GameButton, { PlainGameButton } from '@/components/global/GameButton'; +import React, { useState } from 'react'; +import { toast } from 'react-toastify'; +import { useAccount } from 'wagmi'; + +export default function NavBarMobile({ + type, + bountyId, +}: { + type: 'claim' | 'bounty'; + bountyId?: string; +}) { + const [showForm, setShowForm] = useState(false); + const account = useAccount(); + return ( + <> + + + {type === 'bounty' ? ( + setShowForm(false)} /> + ) : ( + bountyId && ( + setShowForm(false)} + /> + ) + )} + + ); +} diff --git a/src/components/global/Wrapper.tsx b/src/components/global/Wrapper.tsx deleted file mode 100644 index 47bc6833..00000000 --- a/src/components/global/Wrapper.tsx +++ /dev/null @@ -1,25 +0,0 @@ -'use client'; - -import React, { useEffect } from 'react'; - -import '@/styles/globals.css'; -import '@/styles/colors.css'; - -import { useGetChain } from '@/hooks/useGetChain'; -import { useAccount, useSwitchChain } from 'wagmi'; - -export default function Wrapper({ children }: { children: React.ReactNode }) { - const chain = useGetChain(); - const account = useAccount(); - const switchChain = useSwitchChain(); - - useEffect(() => { - if (account.isConnected) { - if (chain.id !== account.chainId) { - switchChain.switchChain({ chainId: chain.id }); - } - } - }, [account, chain, switchChain]); - - return <>{children}; -} diff --git a/src/components/ui/CreateBounty.tsx b/src/components/ui/CreateBounty.tsx index 33d5a7ea..65b95af7 100644 --- a/src/components/ui/CreateBounty.tsx +++ b/src/components/ui/CreateBounty.tsx @@ -13,7 +13,7 @@ export default function CreateBounty() {
{!showForm && (
{ if (account.isConnected) { setShowForm(true); diff --git a/src/components/ui/CreateClaim.tsx b/src/components/ui/CreateClaim.tsx index 6986f71b..5e9cda86 100644 --- a/src/components/ui/CreateClaim.tsx +++ b/src/components/ui/CreateClaim.tsx @@ -25,7 +25,7 @@ export default function CreateClaim({ bountyId }: { bountyId: string }) {
{!showForm && (
{ if (account.isConnected) { setShowForm(true); diff --git a/src/hooks/useScreenSize.ts b/src/hooks/useScreenSize.ts new file mode 100644 index 00000000..5dc76af4 --- /dev/null +++ b/src/hooks/useScreenSize.ts @@ -0,0 +1,18 @@ +import { useState, useEffect } from 'react'; + +export const useScreenSize = () => { + const [isMobile, setIsMobile] = useState(false); + + useEffect(() => { + const checkScreenSize = () => { + setIsMobile(window.innerWidth < 768); + }; + checkScreenSize(); + + window.addEventListener('resize', checkScreenSize); + + return () => window.removeEventListener('resize', checkScreenSize); + }, []); + + return isMobile; +}; diff --git a/src/trpc/routers/_app.ts b/src/trpc/routers/_app.ts index 3e934050..b4d7f1b7 100644 --- a/src/trpc/routers/_app.ts +++ b/src/trpc/routers/_app.ts @@ -41,6 +41,7 @@ export const appRouter = createTRPCRouter({ }, }, include: { + ban: true, claims: { take: 1, }, @@ -59,7 +60,7 @@ export const appRouter = createTRPCRouter({ hasClaims: bounty.claims.length > 0, inProgress: bounty.in_progress, isMultiplayer: bounty.is_multiplayer, - isBanned: bounty.is_banned, + isBanned: bounty.ban.length > 0, isCanceled: bounty.is_canceled, }; }), @@ -77,7 +78,9 @@ export const appRouter = createTRPCRouter({ const items = await prisma.bounties.findMany({ where: { chain_id: input.chainId, - is_banned: false, + ban: { + none: {}, + }, ...(input.status === 'open' ? { in_progress: true, @@ -147,7 +150,9 @@ export const appRouter = createTRPCRouter({ where: { bounty_id: input.bountyId, chain_id: input.chainId, - is_banned: false, + ban: { + none: {}, + }, ...(input.cursor ? { id: { lt: input.cursor } } : {}), }, orderBy: { id: 'desc' }, @@ -183,7 +188,9 @@ export const appRouter = createTRPCRouter({ id: input.claimId, chain_id: input.chainId, }, - is_banned: false, + ban: { + none: {}, + }, }, select: { id: true, @@ -209,7 +216,9 @@ export const appRouter = createTRPCRouter({ where: { issuer: input.address, chain_id: input.chainId, - is_banned: false, + ban: { + none: {}, + }, is_canceled: false, }, select: { @@ -252,7 +261,9 @@ export const appRouter = createTRPCRouter({ where: { issuer: input.address, chain_id: input.chainId, - is_banned: false, + ban: { + none: {}, + }, }, select: { id: true, @@ -462,15 +473,11 @@ export const appRouter = createTRPCRouter({ }); } - await prisma.bounties.update({ - where: { - id_chain_id: { - id: input.id, - chain_id: input.chainId, - }, - }, + await prisma.ban.create({ data: { - is_banned: true, + chain_id: input.chainId, + bounty_id: input.id, + banned_by: input.address.toLowerCase(), }, }); }), @@ -523,15 +530,11 @@ export const appRouter = createTRPCRouter({ }); } - await prisma.claims.update({ - where: { - id_chain_id: { - id: input.id, - chain_id: input.chainId, - }, - }, + await prisma.ban.create({ data: { - is_banned: true, + chain_id: input.chainId, + banned_by: input.address.toLowerCase(), + claim_id: input.id, }, }); }),