diff --git a/gatsby-config.js b/gatsby-config.js index 8b6d3c9cc0..fe56af97f5 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -19,13 +19,6 @@ const plugins = [ name: `orgs`, }, }, - { - resolve: `gatsby-source-filesystem`, - options: { - path: `${__dirname}/_data/handles`, - name: `handles`, - }, - }, { resolve: `gatsby-source-filesystem`, options: { diff --git a/netlify/functions/get-user.ts b/netlify/functions/get-user.ts index 80b388fbd7..cace787fbd 100644 --- a/netlify/functions/get-user.ts +++ b/netlify/functions/get-user.ts @@ -1,4 +1,4 @@ -import { findUser } from "../util/user-utils"; +import { findUser, getUsers } from "../util/user-utils"; exports.handler = async (event) => { // only allow GET @@ -12,11 +12,16 @@ exports.handler = async (event) => { const userHandle = event.queryStringParameters.id; try { - const user = await findUser(userHandle); + let res; + if (userHandle === undefined) { + res = getUsers(); + } else { + res = await findUser(userHandle); + } return { statusCode: 200, - body: JSON.stringify(user), + body: JSON.stringify(res), }; } catch (error) { return { diff --git a/netlify/functions/handles.ts b/netlify/functions/handles.ts new file mode 100644 index 0000000000..5756f6b138 --- /dev/null +++ b/netlify/functions/handles.ts @@ -0,0 +1,59 @@ +import fs, { readFileSync, readdirSync } from "fs"; + +const readAndParseJSONFile = (filePath: string) => { + const buffer = readFileSync(filePath); + return JSON.parse(buffer.toString()); +}; + +const getImageUrl = (fileData: any) => { + if (fileData.image) { + const imagePath = fileData.image.slice(2); + return `https://raw.githubusercontent.com/${process.env.GITHUB_REPO_OWNER}/${process.env.REPO}/${process.env.BRANCH_NAME}/_data/handles/${imagePath}`; + } + return null; +}; + +const getHandles = () => { + const allHandles: { + handle: string; + link: string; + moralisId: string; + imageUrl: string; + members: string[]; + }[] = []; + + const files = readdirSync(`./_data/handles`); + for (const file of files) { + if (file.endsWith(".json")) { + const fileData = readAndParseJSONFile(`./_data/handles/${file}`); + const imageUrl = getImageUrl(fileData); + allHandles.push({ ...fileData, imageUrl }); + } + } + return allHandles; +}; + +exports.handler = async (event) => { + // only allow GET + if (event.httpMethod !== "GET") { + return { + statusCode: 405, + body: "Method not allowed", + headers: { Allow: "GET" }, + }; + } + + try { + // handle + const allHandles = getHandles(); + return { + statusCode: 200, + body: JSON.stringify(allHandles), + }; + } catch (error) { + return { + statusCode: 500, + body: JSON.stringify({ error: error.toString(), details: error.stack }), + }; + } +}; diff --git a/netlify/util/user-utils.ts b/netlify/util/user-utils.ts index 8239dbca74..81609fa1e6 100644 --- a/netlify/util/user-utils.ts +++ b/netlify/util/user-utils.ts @@ -1,4 +1,4 @@ -import { readFileSync } from "fs"; +import { readFileSync, readdirSync } from "fs"; import Moralis from "moralis-v1/node"; import fetch from "node-fetch"; import uniq from "lodash/uniq"; @@ -43,6 +43,61 @@ export async function findUser(userHandle) { } } +export async function getUsers() { + const allHandles: { + handle: string; + link: string; + moralisId: string; + image: string; + }[] = []; + const data = readdirSync(`./_data/handles`); + // just return the objects? + // without members + data.forEach((file) => { + if (file.endsWith(".json")) { + const buffer = readFileSync(`./_data/handles/${file}`); + const wardenFileData = JSON.parse(buffer.toString()); + if (wardenFileData.image) { + const imagePath = wardenFileData.image.slice(2); + wardenFileData.imageUrl = `https://raw.githubusercontent.com/${process.env.GITHUB_REPO_OWNER}/${process.env.REPO}/${process.env.BRANCH_NAME}/_data/handles/${imagePath}`; + } + if (!wardenFileData.members) { + allHandles.push({ ...wardenFileData }); + } + } + }); + return allHandles; +} + +export async function getTeams() { + // just return the objects? + // with members + const teamHandles: { + handle: string; + link: string; + moralisId: string; + image: string; + }[] = []; + const data = readdirSync(`./_data/handles`); + // just return the objects? + // without members + data.forEach((file) => { + if (file.endsWith(".json")) { + const buffer = readFileSync(`./_data/handles/${file}`); + const wardenFileData = JSON.parse(buffer.toString()); + if (wardenFileData.image) { + const imagePath = wardenFileData.image.slice(2); + wardenFileData.imageUrl = `https://raw.githubusercontent.com/${process.env.GITHUB_REPO_OWNER}/${process.env.REPO}/${process.env.BRANCH_NAME}/_data/handles/${imagePath}`; + } + if (wardenFileData.members) { + teamHandles.push({ ...wardenFileData }); + } + } + }); + + return teamHandles; +} + export async function getUserTeams(username: string): Promise { let teamHandles: string[] = []; diff --git a/src/pages/judge-application.js b/src/pages/judge-application.js index 5db6ce8a21..c21a7eb333 100644 --- a/src/pages/judge-application.js +++ b/src/pages/judge-application.js @@ -1,5 +1,4 @@ import React, { useState } from "react"; -import { StaticQuery, graphql } from "gatsby"; import Widgets from "../components/reporter/widgets/Widgets"; import useUser from "../hooks/UserContext"; @@ -87,26 +86,6 @@ const FormStatus = { Error: "error", }; -const wardenListQuery = graphql` - query WardenList { - allHandlesJson(sort: { fields: handle, order: ASC }) { - edges { - node { - id - handle - image { - childImageSharp { - resize(width: 80) { - src - } - } - } - } - } - } - } -`; - const JudgeApplication = () => { const [state, setState] = useState(initialState); const [status, setStatus] = useState("unsubmitted"); @@ -168,113 +147,97 @@ const JudgeApplication = () => { }; return ( - { - const wardens = data.allHandlesJson.edges.map(({ node }) => { - return { value: node.handle, image: node.image }; - }); - fields[0].options = wardens; - - return ( - -
- {(status === FormStatus.Unsubmitted || - status === FormStatus.Submitting) && ( - <> -

Judge Application

-
- {" "} - {status === FormStatus.Error && ( -
-

- An error occurred; please try again. If you continue - to receive this error, let us know in{" "} - Discord. - All fields are required. -

-
- )} - - - - )} - - {status === FormStatus.Submitted && ( -
-

Thanks for applying!

-

- Here's what happens next: -

-
    -
  1. - Judge applications are reviewed by the C4 judge selection - committee, which includes top leaderboard wardens and past - judges. The committee will review your application and - give you a "yes" or "not yet". -
  2. -
  3. - The review process begins after the application window - closes, and we expect it to take about a week, depending - on the number of applications the committee receives. -
  4. -
  5. - You'll be contacted via DM to let you know if your - application has been successful this time around. -
  6. -
  7. - If you're accepted as a judge, an organizer will onboard - you and get you set up to judge your first contest! -
  8. -
+ +
+ {(status === FormStatus.Unsubmitted || + status === FormStatus.Submitting) && ( + <> +

Judge Application

+
+ {" "} + {status === FormStatus.Error && ( +

- In the meantime, if you have questions, feel free to reach - out to us in the C4 Discord, or have a closer look at the{" "} - - How to judge a contest - {" "} - section in the Code4rena docs. + An error occurred; please try again. If you continue to + receive this error, let us know in{" "} + Discord. All + fields are required.

)} - {status === FormStatus.Error && ( -
-

Whoops!

-

An error occurred while processing your application.

- {errorMessage &&

{errorMessage}

} - -
- )} -
-
- ); - }} - /> + + + + )} + + {status === FormStatus.Submitted && ( +
+

Thanks for applying!

+

+ Here's what happens next: +

+
    +
  1. + Judge applications are reviewed by the C4 judge selection + committee, which includes top leaderboard wardens and past + judges. The committee will review your application and give you + a "yes" or "not yet". +
  2. +
  3. + The review process begins after the application window closes, + and we expect it to take about a week, depending on the number + of applications the committee receives. +
  4. +
  5. + You'll be contacted via DM to let you know if your application + has been successful this time around. +
  6. +
  7. + If you're accepted as a judge, an organizer will onboard you and + get you set up to judge your first contest! +
  8. +
+

+ In the meantime, if you have questions, feel free to reach out to + us in the C4 Discord, or have a closer look at the{" "} + + How to judge a contest + {" "} + section in the Code4rena docs. +

+
+ )} + {status === FormStatus.Error && ( +
+

Whoops!

+

An error occurred while processing your application.

+ {errorMessage &&

{errorMessage}

} + +
+ )} +
+ ); }; diff --git a/src/pages/manage-team.tsx b/src/pages/manage-team.tsx index 163d18d259..860075a175 100644 --- a/src/pages/manage-team.tsx +++ b/src/pages/manage-team.tsx @@ -1,4 +1,3 @@ -import { graphql } from "gatsby"; import isEqual from "lodash/isEqual"; import React, { useCallback, useEffect, useState } from "react"; @@ -20,7 +19,7 @@ import { WardenFieldOption } from "../components/reporter/widgets/WardenField"; // styles import * as styles from "../styles/Main.module.scss"; -export default function TeamManagement({ data, location }) { +export default function TeamManagement({ location }) { const { currentUser } = useUser(); const [unauthorized, setIsUnauthorized] = useState(false); @@ -29,6 +28,40 @@ export default function TeamManagement({ data, location }) { WardenFieldOption[] | undefined >(); + // XXX: switching from gatsby graph + const [handles, setHandles] = useState>(new Set()); + const [wardens, setWardens] = useState<{ value: any; image: any }[]>([]); + + // fetch wardens + useEffect(() => { + // // fetch get-user + (async () => { + const result = await fetch(`/.netlify/functions/handles`, { + headers: { + "Content-Type": "application/json", + // "X-Authorization": `Bearer ${sessionToken}`, + // "C4-User": currentUser.username, + }, + }); + if (result.ok) { + let users = await result.json(); + let handles: Set = new Set(users.map((e) => e.handle)); + setHandles(handles); + + let wardens: { value: any; image: any }[] = users.map((e) => { + return { + value: e.handle, + image: e.image ?? "", + }; + }); + setWardens(wardens); + } else { + // @TODO: what to do here? + throw "Unable to fetch user data."; + } + })(); + }, []); + useEffect(() => { // @todo: show error message when user tries to manage a team they are not on (async () => { @@ -95,17 +128,6 @@ export default function TeamManagement({ data, location }) { setTeamState(state); }; - const handles: Set = new Set( - data.handles.edges.map((h) => h.node.handle) - ); - - let wardens: { value: string; image: unknown }[] = []; - data.handles.edges.forEach(({ node }) => { - if (!node.members) { - wardens.push({ value: node.handle, image: node.image }); - } - }); - const onSubmit = useCallback( async (data: TeamCreateRequest, user) => { const sessionToken = user.attributes.sessionToken; @@ -215,27 +237,3 @@ export default function TeamManagement({ data, location }) { ); } - -export const query = graphql` - query { - handles: allHandlesJson(sort: { fields: handle, order: ASC }) { - edges { - node { - handle - link - moralisId - members { - handle - } - image { - childImageSharp { - resize(width: 80) { - src - } - } - } - } - } - } - } -`; diff --git a/src/pages/register-team.tsx b/src/pages/register-team.tsx index d1b3501bcc..df57d949e3 100644 --- a/src/pages/register-team.tsx +++ b/src/pages/register-team.tsx @@ -1,22 +1,45 @@ -import { graphql } from "gatsby"; -import React, { useCallback } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import useUser from "../hooks/UserContext"; import ProtectedPage from "../components/ProtectedPage"; import TeamForm from "../components/TeamForm"; -export default function TeamRegistration({ data }) { +export default function TeamRegistration() { const { currentUser } = useUser(); - const handles: Set = new Set( - data.handles.edges.map((h) => h.node.handle) - ); - let wardens: { value: string; image: unknown }[] = []; - data.handles.edges.forEach(({ node }) => { - if (!node.members) { - wardens.push({ value: node.handle, image: node.image }); - } - }); + // XXX: switching from gatsby graph + const [handles, setHandles] = useState>(new Set()); + const [wardens, setWardens] = useState<{ value: any; image: any }[]>([]); + + // fetch wardens + useEffect(() => { + // // fetch handles + (async () => { + const result = await fetch(`/.netlify/functions/handles`, { + headers: { + "Content-Type": "application/json", + // "X-Authorization": `Bearer ${sessionToken}`, + // "C4-User": currentUser.username, + }, + }); + if (result.ok) { + let users = await result.json(); + let handles: Set = new Set(users.map((e) => e.handle)); + setHandles(handles); + + let wardens: { value: any; image: any }[] = users.map((e) => { + return { + value: e.handle, + image: e.image ?? "", + }; + }); + setWardens(wardens); + } else { + // @TODO: what to do here? + throw "Unable to fetch user data."; + } + })(); + }, []); const onSubmit = useCallback( async (requestBody, user) => { @@ -58,27 +81,3 @@ export default function TeamRegistration({ data }) { ); } - -export const query = graphql` - query { - handles: allHandlesJson(sort: { fields: handle, order: ASC }) { - edges { - node { - handle - link - moralisId - members { - handle - } - image { - childImageSharp { - resize(width: 80) { - src - } - } - } - } - } - } - } -`; diff --git a/src/pages/register.tsx b/src/pages/register.tsx index 9957684c34..75888609a4 100644 --- a/src/pages/register.tsx +++ b/src/pages/register.tsx @@ -1,5 +1,5 @@ import clsx from "clsx"; -import { graphql, navigate } from "gatsby"; +import { navigate } from "gatsby"; import Moralis from "moralis-v1"; import React, { useEffect, useState } from "react"; import { useMoralis } from "react-moralis"; @@ -11,9 +11,10 @@ import RegistrationForm from "../components/RegistrationForm"; import * as styles from "../styles/Main.module.scss"; -export default function UserRegistration({ data }) { - const handles = new Set(data.handles.edges.map((h) => h.node.handle)); - const [wardens, setWardens] = useState([]); +export default function UserRegistration() { + const [handles, setHandles] = useState>(new Set()); + const [wardens, setWardens] = useState<{ value: any; image: any }[]>([]); + const [handleData, setHandleData] = useState([]); const { isInitialized } = useMoralis(); const { currentUser } = useUser(); @@ -23,6 +24,7 @@ export default function UserRegistration({ data }) { } }, [currentUser.isLoggedIn]); + //do we need this function?? It setting wardens with a filtered set of wardens but then wardens is never used?? What am I missing?? useEffect((): void => { async function filterWardens(): Promise { if (!isInitialized) { @@ -31,22 +33,42 @@ export default function UserRegistration({ data }) { const wardensWithSubmissions = await Moralis.Cloud.run( "getWardensWithSubmissions" ); - - const wardens = data.handles.edges - .filter(({ node }) => { - if (node.members) { + const wardens = handleData + .filter((warden) => { + if (warden.warden) { return false; } - if (wardensWithSubmissions.includes(node.handle)) { + if (wardensWithSubmissions.includes(warden.handle)) { return false; } return true; }) - .map(({ node }) => ({ value: node.handle, image: node.image })); + .map((warden) => ({ value: warden.handle, image: warden.image ?? "" })); setWardens(wardens); } filterWardens(); - }, [data, isInitialized]); + }, [isInitialized, handleData]); + + // this is for getting handles from netlify function. + useEffect(() => { + (async () => { + const result = await fetch(`/.netlify/functions/handles`, { + headers: { + "Content-Type": "application/json", + }, + }) + .then((res) => res.json()) + .then((res) => { + return res; + }); + if (result) { + setHandles(new Set(result.map((h) => h.handle))); + setHandleData(result); + } else { + throw "Unable to fetch handle results."; + } + })(); + }, [isInitialized]); return ( ); } - -export const query = graphql` - query { - handles: allHandlesJson(sort: { fields: handle, order: ASC }) { - edges { - node { - handle - link - moralisId - members { - handle - } - image { - childImageSharp { - resize(width: 80) { - src - } - } - } - } - } - } - } -`;