Skip to content

Commit

Permalink
Merge pull request #444 from whtsupbab3/415-display-best-of-poidh
Browse files Browse the repository at this point in the history
feat: Display "best of" poidh on the homepage
  • Loading branch information
picsoritdidnthappen authored Jan 4, 2025
2 parents b139196 + e6c0958 commit 86d6b14
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 21 deletions.
35 changes: 34 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,23 @@

import { NetworkSelector } from '@/components/global/NetworkSelector';
import * as React from 'react';
import { trpc } from '@/trpc/client';

import 'react-toastify/dist/ReactToastify.css';
import PastBountyCard from '@/components/ui/PastBountyCard';
import { Claim } from '@/utils/types';

interface DetailedClaim extends Claim {
chainId: number;
bountyTitle: string;
bountyAmount: string;
}

const Home = () => {
const randomClaims = trpc.randomAcceptedClaims.useQuery({ limit: 24 });

return (
<div className='flex flex-col items-center justify-center text-center p-6 min-h-[85vh]'>
<div className='flex flex-col items-center justify-center text-center p-6 min-h-[85vh] pt-32'>
<h1 className='text-4xl mb-8'>poidh</h1>
<p className='text-lg mb-8'>the easiest way to get stuff done</p>

Expand All @@ -26,6 +39,26 @@ const Home = () => {
</p>
<h3 className='text-2xl mt-8 mb-4'>select a network to get started</h3>
<NetworkSelector height={60} width={60} />
{randomClaims && !randomClaims.error && (
<>
<h3 className='text-2xl mt-8 mb-4'>or browse our past bouties</h3>
{randomClaims.isLoading && (
<p className='animate-pulse mt-5 text-lg'>Loading...</p>
)}
<div className='container mx-auto px-0 py-4 flex flex-col gap-12 lg:grid lg:grid-cols-12 lg:gap-12 lg:px-0 pb-16 mt-5'>
{Array.isArray(randomClaims?.data) &&
randomClaims?.data?.map((claim: DetailedClaim) => (
<PastBountyCard
key={`${claim.id}-${claim.chainId}`}
claim={claim}
chainId={claim.chainId}
bountyTitle={claim.bountyTitle}
bountyAmount={claim.bountyAmount}
/>
))}
</div>
</>
)}
</div>
);
};
Expand Down
11 changes: 1 addition & 10 deletions src/components/bounty/ClaimList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,7 @@ import React from 'react';

import ClaimItem from '@/components/bounty/ClaimItem';
import Voting from '@/components/bounty/Voting';

type Claim = {
id: string;
issuer: string;
bountyId: string;
title: string;
description: string;
accepted: boolean;
url: string;
};
import { Claim } from '@/utils/types';

export default function ClaimList({
bountyId,
Expand Down
11 changes: 1 addition & 10 deletions src/components/bounty/ClaimListAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,7 @@ import React, { useEffect, useState } from 'react';

import { useGetChain } from '@/hooks/useGetChain';
import { CopyDoneIcon, CopyIcon } from '@/components/global/Icons';

type Claim = {
id: string;
title: string;
description: string;
url: string;
issuer: string;
bountyId: string;
accepted: boolean;
};
import { Claim } from '@/utils/types';

export default function ClaimsListAccount({ claims }: { claims: Claim[] }) {
return (
Expand Down
109 changes: 109 additions & 0 deletions src/components/ui/PastBountyCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import Link from 'next/link';
import React, { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { CopyIcon } from '@/components/global/Icons';
import { chains } from '@/utils/config';
import { Claim } from '@/utils/types';
import { Chain } from '@/utils/types';
import { useRouter } from 'next/navigation';
import { formatEther } from 'viem';

const getChainById = (chainId: number): Chain | undefined =>
Object.values(chains).find((chain) => chain.id === chainId);

export default function PastBountyCard({
claim,
chainId,
bountyTitle,
bountyAmount,
}: {
claim: Claim;
chainId: number;
bountyTitle: string;
bountyAmount: string;
}) {
const router = useRouter();
const [imageUrl, setImageUrl] = useState<string | null>(null);

const chain = getChainById(chainId);

const fetchImageUrl = async (url: string) => {
const response = await fetch(url);
const data = await response.json();
setImageUrl(data.image);
};

useEffect(() => {
fetchImageUrl(claim?.url);
}, [claim]);

return (
<>
{claim && (
<div
className='lg:col-span-4 p-3 bg-whiteblue border-1 rounded-xl cursor-pointer'
onClick={() => {
const chainName = chain?.slug;
router.push(`/${chainName}/bounty/${claim.bountyId}`);
}}
>
<div className='p-[2px] text-white relative bg-[#F15E5F] border-[#F15E5F] border-2 rounded-xl'>
<div>
{claim.accepted && (
<div className='left-5 top-5 text-white bg-[#F15E5F] border border-[#F15E5F] rounded-[8px] py-2 px-5 absolute'>
accepted
</div>
)}
<div
style={{ backgroundImage: `url(${imageUrl})` }}
className='bg-[#12AAFF] bg-cover bg-center w-full aspect-w-1 aspect-h-1 rounded-[8px] overflow-hidden'
></div>
<div className='p-3'>
<div className='flex flex-col'>
<p className='normal-case text-nowrap overflow-ellipsis overflow-hidden break-word text-left'>
{claim.title}
</p>
<p className='normal-case w-full h-20 overflow-y-auto overflow-x-hidden overflow-hidden break-words text-left'>
{claim.description}
</p>
</div>
<div className='mt-2 py-2 flex flex-row justify-between text-sm border-t border-dashed'>
<span className=''>issuer</span>
<span className='flex flex-row'>
<Link
href={`/${chain?.slug}/account/${claim?.issuer}`}
className='hover:text-gray-200'
>
{claim?.issuer.slice(0, 5) +
'…' +
claim?.issuer.slice(-6)}
</Link>
<span className='ml-1 text-white'>
<button
onClick={async () => {
await navigator.clipboard.writeText(claim?.issuer);
toast.success('Address copied to clipboard');
}}
>
<CopyIcon width={16} height={16} />
</button>
</span>
</span>
</div>
<div className='text-left'>claim id: {claim?.id}</div>
</div>
</div>
</div>
<div className='px-1 py-3 text-left'>
<p className='text-nowrap overflow-ellipsis overflow-hidden text-xl mb-3 normal-case'>
{bountyTitle}
</p>
<span className='text-md'>
{formatEther(BigInt(bountyAmount))} {chain?.currency}
</span>
</div>
</div>
)}
</>
);
}
28 changes: 28 additions & 0 deletions src/trpc/routers/_app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,34 @@ export const appRouter = createTRPCRouter({
};
}),

randomAcceptedClaims: baseProcedure
.input(
z.object({
limit: z.number().min(0).default(24),
})
)
.query(async ({ input }) => {
return await prisma.$queryRaw`
SELECT c.*,
c.chain_id AS "chainId",
c.is_accepted AS "accepted",
c.bounty_id AS "bountyId",
b.title AS "bountyTitle",
b.amount AS "bountyAmount"
FROM "Claims" c
JOIN (
SELECT id, chain_id, title, amount
FROM "Bounties"
WHERE in_progress IS FALSE
AND is_canceled IS FALSE
AND is_voting IS FALSE
) b ON c.bounty_id = b.id AND c.chain_id = b.chain_id
WHERE c.is_accepted IS TRUE
ORDER BY RANDOM()
LIMIT ${input.limit};
`;
}),

participations: baseProcedure
.input(z.object({ bountyId: z.number(), chainId: z.number() }))
.query(async ({ input }) => {
Expand Down
10 changes: 10 additions & 0 deletions src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,13 @@ export type Wallet = {
ens: string | null;
degenName: string | null;
};

export type Claim = {
id: string;
title: string;
description: string;
url: string;
issuer: string;
bountyId: string;
accepted: boolean;
};

0 comments on commit 86d6b14

Please sign in to comment.