Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 4 additions & 12 deletions packages/nextjs/app/_components/ApplyEligibilityLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import Link from "next/link";
import { useConnectModal } from "@rainbow-me/rainbowkit";
import { useAccount } from "wagmi";
import { LockClosedIcon, LockOpenIcon } from "@heroicons/react/24/outline";
import { useBGBuilderData } from "~~/hooks/useBGBuilderData";
import { useSpeedRunChallengeEligibility } from "~~/hooks/useSpeedRunChallengeEligibility";
import { REQUIRED_CHALLENGE_COUNT } from "~~/utils/eligibility-criteria";

Expand Down Expand Up @@ -61,25 +60,18 @@ const FeedbackMessage = ({

export const ApplyEligibilityLink = () => {
const { isConnected, address: connectedAddress } = useAccount();
const { isBuilderPresent, isLoading: isFetchingBuilderData } = useBGBuilderData(connectedAddress);
const { openConnectModal } = useConnectModal();
const {
isLoading: isLoadingSRE,
isEligible: isEligibleSRE,
completedChallengesCount,
} = useSpeedRunChallengeEligibility(connectedAddress);
const { isLoading, isEligible, completedChallengesCount } = useSpeedRunChallengeEligibility(connectedAddress);

let builderStatus: BuilderStatus = "notConnected";
if (!isConnected || isLoadingSRE) {
if (!isConnected || isLoading) {
builderStatus = "notConnected";
} else if (isEligibleSRE || isBuilderPresent) {
} else if (isEligible) {
builderStatus = "eligible";
} else {
builderStatus = "notElegible";
}

const isFetching = isLoadingSRE || (isEligibleSRE === false && isFetchingBuilderData);

return (
<div className="mx-auto lg:m-0 flex flex-col items-start bg-white px-6 py-2 pb-6 font-spaceGrotesk space-y-1 w-4/5 rounded-2xl text-left">
<p className="text-2xl font-semibold mb-0">Do you qualify?</p>
Expand All @@ -101,7 +93,7 @@ export const ApplyEligibilityLink = () => {
if (!isConnected && openConnectModal) openConnectModal();
}}
>
{isFetching ? (
{isLoading ? (
<span className="loading loading-spinner h-5 w-5"></span>
) : (
<LockClosedIcon className="h-5 w-5 mr-1 inline-block" />
Expand Down
13 changes: 7 additions & 6 deletions packages/nextjs/app/_components/EcosystemGrants.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import Image from "next/image";
import XIcon from "~~/components/assets/XIcon";
import { getAllEcosystemGrants } from "~~/services/database/grants";

const EcosystemGrantsCard = ({
title,
description,
imageLink,
amountGranted,
twitterLink,
xLink,
}: {
title: string;
description: string;
imageLink: string;
amountGranted: string;
twitterLink: string;
xLink: string;
}) => {
return (
<div className="bg-base-100 rounded-2xl min-h-[380px] max-w-[370px] flex flex-col">
Expand All @@ -24,13 +25,13 @@ const EcosystemGrantsCard = ({
</div>
<div className="flex-1 flex flex-col items-start justify-between space-y-4 p-5">
<p className="text-sm m-0 font-spaceMono font-normal leading-5 pb-2">{description}</p>
<div className="flex justify-between items-baseline w-full">
<div className="flex justify-between align-center items-center w-full">
<div className="bg-primary rounded-lg py-1 px-2 text-xs font-bold">
Amount:
<span className="text-sm"> {Number(amountGranted).toFixed(2)} ETH</span>
</div>
<a href={twitterLink} target="_blank" className="text-sm underline underline-offset-1">
Twitter
<a href={xLink} target="_blank" className="inline-block w-[20px] hover:opacity-80">
<XIcon />
</a>
</div>
</div>
Expand Down Expand Up @@ -58,7 +59,7 @@ export const EcosystemGrants = async () => {
description={grant.description}
imageLink={grant.imgLink}
amountGranted={grant.amountGranted}
twitterLink={grant.twitterLink}
xLink={grant.xLink}
/>
))}
</div>
Expand Down
26 changes: 10 additions & 16 deletions packages/nextjs/app/admin/_components/GrantReview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,32 @@ import { useNetwork } from "wagmi";
import { ArrowTopRightOnSquareIcon, QuestionMarkCircleIcon } from "@heroicons/react/20/solid";
import { PencilSquareIcon } from "@heroicons/react/24/outline";
import TelegramIcon from "~~/components/assets/TelegramIcon";
import TwitterIcon from "~~/components/assets/TwitterIcon";
import XIcon from "~~/components/assets/XIcon";
import { Address } from "~~/components/scaffold-eth";
import { useScaffoldContractWrite } from "~~/hooks/scaffold-eth";
import { GrantData, GrantDataWithPrivateNote, SocialLinks } from "~~/services/database/schema";
import { SocialLinks } from "~~/services/api/sre/schema";
import { GrantData, GrantDataWithPrivateNote } from "~~/services/database/schema";
import { PROPOSAL_STATUS, ProposalStatusType } from "~~/utils/grants";

const BuilderSocials = ({ socialLinks }: { socialLinks?: SocialLinks }) => {
if (!socialLinks) return null;

return (
<>
{socialLinks?.twitter && (
{socialLinks?.socialX && (
<a
className="inline-block w-[20px] hover:opacity-80"
href={`https://twitter.com/${socialLinks?.twitter}`}
href={`https://x.com/${socialLinks?.socialX}`}
target="_blank"
rel="noreferrer"
>
<TwitterIcon />
<XIcon />
</a>
)}
{socialLinks?.telegram && (
{socialLinks?.socialTelegram && (
<a
className="inline-block w-[20px] hover:opacity-80"
href={`https://telegram.me/${socialLinks?.telegram}`}
href={`https://telegram.me/${socialLinks?.socialTelegram}`}
target="_blank"
rel="noreferrer"
>
Expand Down Expand Up @@ -160,16 +161,9 @@ export const GrantReview = ({ grant, selected, toggleSelection }: GrantReviewPro
</div>
<div className="flex gap-4 items-center mt-3">
<BuilderSocials socialLinks={grant.builderData?.socialLinks} />
{grant.builderData?.batch?.number && (
<div className="badge badge-outline">Batch #{grant.builderData.batch?.number}</div>
{grant.builderData?.batchId?.number && (
<div className="badge badge-outline">Batch #{grant.builderData.batchId?.number}</div>
)}
{grant.builderData?.builderCohort?.map(cohort => {
return (
<a href={cohort.url} target="_blank" rel="noreferrer" key={cohort.id} className="link">
<div className="badge badge-secondary">{cohort.name}</div>
</a>
);
})}
</div>
</div>
<div className="p-4">
Expand Down
7 changes: 4 additions & 3 deletions packages/nextjs/app/api/admin/signin/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NextResponse } from "next/server";
import { recoverTypedDataAddress } from "viem";
import { findUserByAddress } from "~~/services/database/users";
import { fetchBuilderData } from "~~/services/api/sre/builders";
import { EIP_712_DOMAIN, EIP_712_TYPES__ADMIN_SIGN_IN } from "~~/utils/eip712";
import { validateSafeSignature } from "~~/utils/safe-signature";

Expand All @@ -10,6 +10,7 @@ type AdminSignInBody = {
isSafeSignature?: boolean;
chainId?: number;
};

export async function POST(req: Request) {
try {
const { signer, signature, isSafeSignature, chainId } = (await req.json()) as AdminSignInBody;
Expand All @@ -18,8 +19,8 @@ export async function POST(req: Request) {
return new Response("Missing signer or signature", { status: 400 });
}

const signerData = await findUserByAddress(signer);
if (signerData.data?.role !== "admin") {
const userData = await fetchBuilderData(signer);
if (userData?.role !== "admin") {
console.error("Unauthorized", signer);
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
Expand Down
17 changes: 0 additions & 17 deletions packages/nextjs/app/api/builders/[builderAddress]/route.ts

This file was deleted.

8 changes: 4 additions & 4 deletions packages/nextjs/app/api/grants/[grantId]/review/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { NextRequest, NextResponse } from "next/server";
import { EIP712TypedData } from "@safe-global/safe-core-sdk-types";
import { waitUntil } from "@vercel/functions";
import { recoverTypedDataAddress } from "viem";
import { fetchBuilderData } from "~~/services/api/sre/builders";
import { getGrantById, reviewGrant } from "~~/services/database/grants";
import { findUserByAddress } from "~~/services/database/users";
import { extractBuildId, sendBuildToSRE } from "~~/services/sre";
import { EIP_712_DOMAIN, EIP_712_TYPES__REVIEW_GRANT, EIP_712_TYPES__REVIEW_GRANT_WITH_NOTE } from "~~/utils/eip712";
import { PROPOSAL_STATUS, ProposalStatusType } from "~~/utils/grants";
Expand Down Expand Up @@ -89,9 +89,9 @@ export async function POST(req: NextRequest, { params }: { params: { grantId: st
}

// Only admins can review grants
const signerData = await findUserByAddress(signer);

if (signerData.data?.role !== "admin") {
const signerData = await fetchBuilderData(signer);
if (signerData?.role !== "admin") {
console.error("Unauthorized", signer);
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

Expand Down
6 changes: 3 additions & 3 deletions packages/nextjs/app/api/grants/[grantId]/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { NextRequest, NextResponse } from "next/server";
import { EIP712TypedData } from "@safe-global/safe-core-sdk-types";
import { recoverTypedDataAddress } from "viem";
import { fetchBuilderData } from "~~/services/api/sre/builders";
import { updateGrant } from "~~/services/database/grants";
import { findUserByAddress } from "~~/services/database/users";
import { EIP_712_DOMAIN, EIP_712_TYPES__EDIT_GRANT } from "~~/utils/eip712";
import { validateSafeSignature } from "~~/utils/safe-signature";

Expand Down Expand Up @@ -55,8 +55,8 @@ export async function PATCH(req: NextRequest, { params }: { params: { grantId: s
}

// Only admins can edit grant
const signerData = await findUserByAddress(signer);
if (signerData.data?.role !== "admin") {
const signerData = await fetchBuilderData(signer);
if (signerData?.role !== "admin") {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
await updateGrant(grantId, { title, description, askAmount }, private_note);
Expand Down
17 changes: 3 additions & 14 deletions packages/nextjs/app/api/grants/new/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { NextResponse } from "next/server";
import { recoverTypedDataAddress } from "viem";
import { createGrant } from "~~/services/database/grants";
import { findUserByAddress } from "~~/services/database/users";
import { EIP_712_DOMAIN, EIP_712_TYPES__APPLY_FOR_GRANT } from "~~/utils/eip712";
import { REQUIRED_CHALLENGE_COUNT, fetchAcceptedChallengeCount } from "~~/utils/eligibility-criteria";

Expand All @@ -24,19 +23,9 @@ export async function POST(req: Request) {
return NextResponse.json({ error: "Invalid form details submitted" }, { status: 400 });
}

// Verify if the builder is present (legacy BG check)
const builder = await findUserByAddress(signer);
let eligible = false;
if (builder.exists) {
eligible = true;
} else {
// New SRE challenge check
const completed = await fetchAcceptedChallengeCount(signer);
if (completed >= REQUIRED_CHALLENGE_COUNT) {
eligible = true;
}
}
if (!eligible) {
// Legacy BG builder presence check removed. All eligibility is now based on the new SpeedRunEthereum system. If needed we could try do some kind of ROLE validation and make sure OG BuidlGuidl members that that certain ROLE in new SRE database.
const completed = await fetchAcceptedChallengeCount(signer);
if (completed < REQUIRED_CHALLENGE_COUNT) {
return NextResponse.json(
{
error: `Only builders with at least ${REQUIRED_CHALLENGE_COUNT} accepted SpeedRun Ethereum challenges can submit for grants`,
Expand Down
10 changes: 5 additions & 5 deletions packages/nextjs/app/api/grants/review/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { headers } from "next/headers";
import { NextRequest, NextResponse } from "next/server";
import { EIP712TypedData } from "@safe-global/safe-core-sdk-types";
import { recoverTypedDataAddress } from "viem";
import { fetchBuilderData } from "~~/services/api/sre/builders";
import { getAllGrantsForReview, reviewGrant } from "~~/services/database/grants";
import { findUserByAddress } from "~~/services/database/users";
import { EIP_712_DOMAIN, EIP_712_TYPES__REVIEW_GRANT_BATCH } from "~~/utils/eip712";
import { PROPOSAL_STATUS, ProposalStatusType } from "~~/utils/grants";
import { validateSafeSignature } from "~~/utils/safe-signature";
Expand All @@ -26,8 +26,8 @@ export async function GET() {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

const signerData = await findUserByAddress(address);
if (signerData.data?.role !== "admin") {
const signerData = await fetchBuilderData(address);
if (signerData?.role !== "admin") {
console.error("Unauthorized", address);
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
Expand Down Expand Up @@ -76,8 +76,8 @@ export async function POST(req: NextRequest) {
}

// Only admins can review grants
const signerData = await findUserByAddress(signer);
if (signerData.data?.role !== "admin") {
const signerData = await fetchBuilderData(signer);
if (signerData?.role !== "admin") {
console.error("Unauthorized", signer);
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
Expand Down
14 changes: 3 additions & 11 deletions packages/nextjs/app/apply/_component/SubmitButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,16 @@

import { useFormStatus } from "react-dom";
import { useAccount } from "wagmi";
import { useBGBuilderData } from "~~/hooks/useBGBuilderData";
import { useSpeedRunChallengeEligibility } from "~~/hooks/useSpeedRunChallengeEligibility";
import { REQUIRED_CHALLENGE_COUNT } from "~~/utils/eligibility-criteria";

// To use useFormStatus we need to make sure button is child of form
const SubmitButton = () => {
const { pending } = useFormStatus();
const { isConnected, address: connectedAddress } = useAccount();
const { isBuilderPresent, isLoading: isFetchingBuilderData } = useBGBuilderData(connectedAddress);
const {
isLoading: isLoadingSRE,
isEligible: isEligibleSRE,
completedChallengesCount,
} = useSpeedRunChallengeEligibility(connectedAddress);
const { isLoading, isEligible, completedChallengesCount } = useSpeedRunChallengeEligibility(connectedAddress);

const isEligible = isEligibleSRE || isBuilderPresent;
const isFetching = isLoadingSRE || (isEligibleSRE === false && isFetchingBuilderData);
const isSubmitDisabled = !isConnected || isFetching || !isEligible || pending;
const isSubmitDisabled = !isConnected || isLoading || !isEligible || pending;

let tooltip = "";
if (!isConnected) {
Expand All @@ -33,7 +25,7 @@ const SubmitButton = () => {
return (
<div className={`flex ${(!isConnected || !isEligible) && "tooltip tooltip-bottom"}`} data-tip={tooltip}>
<button className="btn btn-primary w-full" disabled={isSubmitDisabled} aria-disabled={isSubmitDisabled}>
{(isFetching || pending) && <span className="loading loading-spinner loading-md"></span>}
{(isLoading || pending) && <span className="loading loading-spinner loading-md"></span>}
Submit
</button>
</div>
Expand Down
13 changes: 0 additions & 13 deletions packages/nextjs/components/assets/TwitterIcon.tsx

This file was deleted.

12 changes: 12 additions & 0 deletions packages/nextjs/components/assets/XIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { SVGProps } from "react";

type XIconProps = SVGProps<SVGSVGElement>;

const XIcon = ({ ...props }: XIconProps) => (
<svg role="img" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg" {...props}>
<title>X</title>
<path d="M 6.9199219 6 L 21.136719 26.726562 L 6.2285156 44 L 9.40625 44 L 22.544922 28.777344 L 32.986328 44 L 43 44 L 28.123047 22.3125 L 42.203125 6 L 39.027344 6 L 26.716797 20.261719 L 16.933594 6 L 6.9199219 6 z" />
</svg>
);

export default XIcon;
15 changes: 8 additions & 7 deletions packages/nextjs/hooks/useBGBuilderData.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import useSWRImmutable from "swr/immutable";
import { BuilderDataResponse } from "~~/services/database/schema";
import { BuilderData } from "~~/services/api/sre/schema";
import { fetchBuilderData } from "~~/services/api/sre/builders";

export const useBGBuilderData = (address?: string) => {
const {
data: responseData,
data,
isLoading,
error,
} = useSWRImmutable<BuilderDataResponse>(address ? `/api/builders/${address}` : null);
} = useSWRImmutable<BuilderData | undefined>(
address ? `builder-${address}` : null,
() => fetchBuilderData(address!)
);

const data = responseData?.data;
const isBuilderPresent = responseData?.exists ?? false;

return { isLoading, error, data, isBuilderPresent };
return { isLoading, error, data };
};
Loading