Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrates project submission to judging #58

Draft
wants to merge 28 commits into
base: rewrite-nova
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
53b4e66
implements getProjects trpc procedure
anudaweerasinghe Mar 26, 2024
9b3ee2a
Adds getUserProject trpc procedure
anudaweerasinghe Mar 26, 2024
86fd897
implements saveProject trpc procedure
anudaweerasinghe Mar 26, 2024
c56328b
implements submit and unsubmit project to a prize routes
anudaweerasinghe Mar 26, 2024
7f4699e
project submission page wip
anudaweerasinghe Mar 26, 2024
5ca7444
fixes prize/project relations in prisma schema
anudaweerasinghe Mar 27, 2024
50d0ab0
fixes project procedures to match correct schema
anudaweerasinghe Mar 27, 2024
7f6ebfd
implements project submission form
anudaweerasinghe Mar 27, 2024
526aaed
implements project submit button
anudaweerasinghe Mar 27, 2024
6bcf677
project submission and update logic complete
anudaweerasinghe Mar 27, 2024
31defaf
adds user email if it doesn't exist
anudaweerasinghe Mar 27, 2024
88905d3
adds user email if it doesn't exist
anudaweerasinghe Mar 27, 2024
7b8c601
refactors project details into separate component
anudaweerasinghe Mar 27, 2024
26703cd
refactors project details into separate component
anudaweerasinghe Mar 27, 2024
7f85920
adds nullish coalescing of otherResources to empty string
anudaweerasinghe Mar 29, 2024
0ea3972
adds trim to each individual email
anudaweerasinghe Mar 29, 2024
83d170d
Add error handling and error codes in projects router
anudaweerasinghe Mar 29, 2024
f42a1aa
uses single prisma query to get user project
anudaweerasinghe Mar 29, 2024
f2e6634
removes console.log in project router
anudaweerasinghe Mar 29, 2024
ef54628
switches from using promise to prisma. on project save
anudaweerasinghe Mar 29, 2024
41188b7
adds check that team members aren't on a different team
anudaweerasinghe Mar 30, 2024
df99eaa
increases project submit form width
anudaweerasinghe Mar 30, 2024
d5dda3d
tabs styling wip
anudaweerasinghe Mar 30, 2024
b7ef5d5
implements tabs
anudaweerasinghe Mar 30, 2024
1e5e18f
adds drop shadow to tabs
anudaweerasinghe Mar 30, 2024
e982b83
adds new tabs to admin page
anudaweerasinghe Mar 30, 2024
d7b5765
implements get user prizes endpoint
anudaweerasinghe Mar 30, 2024
b391748
prize submission cards wip
anudaweerasinghe Mar 30, 2024
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
5 changes: 1 addition & 4 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,6 @@ model Prize {
judgeAssignments JudgePrizeAssignment[]
judgingInstances JudgingInstance[]
judgments ProjectComparisonResult[]
Project Project? @relation(fields: [projectId], references: [id])
projectId String?
}

// Projects are compared against each other by judges
Expand All @@ -97,8 +95,7 @@ model Project {
teamName String
teamMembers User[]

prizes Prize[]
leadingPrizes JudgePrizeAssignment[]
prizes JudgePrizeAssignment[]
incomingJudges Judge[]
judgingInstances JudgingInstance[]
ignores IgnoreProjects[]
Expand Down
15 changes: 10 additions & 5 deletions src/components/UserTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,24 @@ export default function UserTable() {
}

if (users && users.length === 0) {
return <Alert message="No users found. Users can be added on the Settings page" type="info" />;
return (
<Alert
message="No users found. Users can be added on the Settings page"
type="info"
/>
);
}

return (
<div className="flex flex-col gap-8">
<div className="flex w-full flex-col gap-8">
<input
type="text"
placeholder="Search by email or role"
value={search}
onChange={(e) => setSearch(e.target.value)}
className="rounded-md border-2 border-purple p-2"
/>
<table className="m-auto">
<table className="m-auto w-full">
<tbody>
{users
?.filter((user) => {
Expand Down Expand Up @@ -85,7 +90,7 @@ export default function UserTable() {
<td className="px-4">
{!user.isAdmin && (
<button
className="bg-purple my-2 rounded-md p-2 text-white"
className="my-2 rounded-md bg-purple p-2 text-white"
onClick={() => {
promoteToAdmin(user.email);
}}
Expand All @@ -97,7 +102,7 @@ export default function UserTable() {
<td className="px-4">
{user.type !== UserType.JUDGE && (
<button
className="bg-blue my-2 rounded-md p-2 text-white"
className="my-2 rounded-md bg-blue p-2 text-white"
onClick={() => {
promoteToJudge(user.email);
}}
Expand Down
2 changes: 1 addition & 1 deletion src/components/admin/AdminForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function AdminForm() {
return (
<>
{isLoading && <Spinner />}
<div className={isLoading ? "hidden" : "flex w-96 flex-col gap-8"}>
<div className={isLoading ? "hidden" : "flex w-full flex-col gap-8"}>
<AdminSettings
setError={setError}
setIsLoading={setIsLoading}
Expand Down
2 changes: 1 addition & 1 deletion src/components/admin/PrizeTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default function PrizeTab() {
}

return (
<div className="m-auto flex min-w-[60rem] flex-col gap-5 pb-8">
<div className="m-auto flex w-full min-w-[60rem] flex-col gap-5 pb-8">
<div>
<h3 className="font-bold">General prizes</h3>
<textarea
Expand Down
38 changes: 38 additions & 0 deletions src/components/projectSubmission/PrizeSelection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { api } from "../../utils/api";
import Spinner from "../Spinner";
import PrizeSubmitCard from "./PrizeSubmitCard";

export default function ProjectDetailsForm() {
const {
isFetching,
data: prizes,
refetch,
} = api.projects.getPrizesWithSubmissionStatus.useQuery();

const { isFetching: isFetchingProject, data: project } =
api.projects.getUserProject.useQuery();

return isFetching || isFetchingProject ? (
<Spinner />
) : (
<div className="flex w-full flex-wrap items-center">
{project ? (
prizes?.map((prize) => (
<PrizeSubmitCard
key={prize.id}
projectId={project.id}
prizeName={prize.name}
prizeDescription={prize.description}
/>
))
) : (
<div
className="w-full rounded-lg bg-red-100 py-2 text-center text-base text-red-700"
role="alert"
>
You need to save your project details before submitting for prizes!
</div>
)}
</div>
);
}
33 changes: 33 additions & 0 deletions src/components/projectSubmission/PrizeSubmitCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { ReactElement } from "react";
import Button from "../Button";
import { api } from "../../utils/api";

interface Props {
prizeName: string;
prizeDescription: string;
projectId: string;
}

/**
* A prize card displayed on the landing page
*/
export default function PrizeCard({
prizeName,
projectId,
prizeDescription,
}: Props): ReactElement {
return (
<div className="w-1/2 p-2">
<div className="rounded-2xl bg-white py-3 px-5 text-black drop-shadow">
<div className="xl mb-2 flex flex-row gap-3 text-xl font-bold">
<h1>{prizeName}</h1>
</div>
<p className="mb-2 break-normal">{prizeDescription}</p>
<Button
text="Submit for Prize"
className="m-0 w-full bg-green-300 text-gray-600 drop-shadow-none"
/>
</div>
</div>
);
}
166 changes: 166 additions & 0 deletions src/components/projectSubmission/ProjectDetailsForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { useEffect, useState } from "react";
import { api } from "../../utils/api";
import Button from "../Button";
import Spinner from "../Spinner";

interface Props {
email: string;
}

export default function ProjectDetailsForm({ email }: Props) {
const {
isFetching,
data: project,
refetch,
} = api.projects.getUserProject.useQuery();

const [teamName, setTeamName] = useState(project?.teamName);
const [projectName, setProjectName] = useState(project?.name);
const [description, setDescription] = useState(project?.description);
const [githubUrl, setGithubUrl] = useState(project?.githubUrl ?? "");
const [otherResources, setOtherResources] = useState(
project?.otherResources ?? ""
);
const [teamMembers, setTeamMembers] = useState(
teamMembersToString(
project?.teamMembers.map((member) => member.email) ?? []
)
);

useEffect(() => {
if (project) {
setTeamName(project.teamName);
setProjectName(project.name);
setDescription(project.description);
setGithubUrl(project.githubUrl ?? "");
setOtherResources(project.otherResources ?? "");
setTeamMembers(
teamMembersToString(
project?.teamMembers.map((member) => member.email) ?? []
)
);
} else {
setTeamMembers(email);
}
}, [project, email]);
const { isLoading, mutate: saveProject } =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to have some failure logic to let the user know some of the inputs are incorrect

api.projects.saveProject.useMutation({
onSuccess: async () => {
await refetch();
},
});

function teamMembersToString(teamMembers: string[]) {
return teamMembers.join(", ");
}

function teamMembersToList(teamMembers: string) {
const emails = teamMembers.split(",");
return emails.map((email) => email.trim());
}

return isFetching || isLoading ? (
<Spinner />
) : (
<>
<div className="flex w-full flex-col gap-2">
<label htmlFor="teamName" className="font-bold">
Team Name*
</label>
<input
type="text"
id="teamName"
placeholder="Enter team name"
value={teamName}
required
onChange={(e) => setTeamName(e.target.value)}
className="border-grey w-full rounded-md border-2 p-2"
/>
</div>

<div className="flex w-full flex-col gap-2">
<label htmlFor="teamMembers" className="font-bold">
Team Member Emails*
</label>
<input
type="text"
id="teamMembers"
placeholder="Enter team member emails (comma separated)"
value={teamMembers}
required
onChange={(e) => setTeamMembers(e.target.value)}
className="border-grey w-full rounded-md border-2 p-2"
/>
</div>

<div className="flex w-full flex-col gap-2">
<label htmlFor="projectName" className="font-bold">
Project Name*
</label>
<input
type="text"
id="projectName"
placeholder="Enter project name"
value={projectName}
required
onChange={(e) => setProjectName(e.target.value)}
className="border-grey w-full rounded-md border-2 p-2"
/>
</div>

<div className="flex w-full flex-col gap-2">
<label htmlFor="githubUrl" className="font-bold">
GitHub Link
</label>
<input
type="text"
id="githubUrl"
placeholder="Enter GitHub link"
value={githubUrl}
onChange={(e) => setGithubUrl(e.target.value)}
className="border-grey w-full rounded-md border-2 p-2"
/>
</div>

<div className="flex w-full flex-col gap-2">
<label htmlFor="description" className="font-bold">
Description*
</label>
<textarea
id="description"
placeholder="Enter project description"
value={description}
onChange={(e) => setDescription(e.target.value)}
className="border-grey w-full rounded-md border-2 p-2"
></textarea>
</div>

<div className="flex w-full flex-col gap-2">
<label htmlFor="otherResources" className="font-bold">
Other Resources
</label>
<textarea
id="otherResources"
placeholder="Links to other resources associated with your project."
value={otherResources}
onChange={(e) => setOtherResources(e.target.value)}
className="border-grey w-full rounded-md border-2 p-2"
></textarea>
</div>
<Button
text="Save"
className="m-4 w-full font-bold"
onClick={() => {
saveProject({
name: projectName ?? "",
githubUrl: githubUrl,
teamName: teamName ?? "",
description: description ?? "",
otherResources: otherResources ?? "",
teamMembers: teamMembersToList(teamMembers),
});
}}
/>
</>
);
}
27 changes: 19 additions & 8 deletions src/pages/admin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ export default function Admin() {
return (
<>
<Header showAdmin />
<main className="flex flex-col items-center gap-5 py-5 px-2 md:px-10">
<main className="flex w-full flex-col items-center gap-5 py-5 px-2 md:px-10">
<h1 className="text-3xl font-bold">Admin</h1>
<Tabs defaultValue="settings">
<TabsList className="flex flex-row gap-8 pb-8">
<Tabs defaultValue="settings" className="w-8/12">
<TabsList className="mb-8 flex w-full flex-row bg-gray-200 p-1">
{tabs
.filter(
(tab) =>
Expand All @@ -43,22 +43,33 @@ export default function Admin() {
<TabsTrigger
key={tab.key}
value={tab.key}
className={`m-0 rounded-md ${
selectedTab === tab.key ? "bg-purple text-white" : ""
className={`m-0 w-full rounded-md font-semibold ${
selectedTab == tab.key
? "bg-white text-gray-800 drop-shadow-sm"
: "text-gray-500"
}`}
onClick={() => setSelectedTab(tab.key)}
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
<TabsContent value="settings">
<TabsContent
value="settings"
className=" flex w-full flex-col items-center justify-center gap-3"
>
<AdminForm />
</TabsContent>
<TabsContent value="users">
<TabsContent
value="users"
className=" flex w-full flex-col items-center justify-center gap-3"
>
<UserTable />
</TabsContent>
<TabsContent value="prizes">
<TabsContent
value="prizes"
className=" flex w-full flex-col items-center justify-center gap-3"
>
<PrizeTab />
</TabsContent>
</Tabs>
Expand Down
Loading