Skip to content

Commit b7563c8

Browse files
committed
feat: add table matching page, add toasts for everything, mark as
complete button
1 parent d194e23 commit b7563c8

File tree

7 files changed

+170
-19
lines changed

7 files changed

+170
-19
lines changed

frontend/src/app/admin/portal/page.tsx

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,43 @@
11
"use client";
22

3+
import { fetchAllUsers, fetchIsAdmin, promoteToAdmin } from "@/app/api";
34
import Button from "@/app/components/button/Button";
45
import useAdmin from "@/app/hooks/useAdmin";
6+
import { message } from "antd";
57
import { useRouter } from "next/navigation";
6-
import { use, useState } from "react";
8+
import { use, useEffect, useState } from "react";
79
import Select, { MultiValue } from "react-select";
810

911
const AdminPortalPage = () => {
1012
const isAdmin = useAdmin();
1113
const router = useRouter();
14+
const [api, contextHolder] = message.useMessage();
15+
16+
type User = {
17+
uid: string;
18+
name: string;
19+
imageUrl: string | null;
20+
preferredLang: string | null;
21+
role: string;
22+
};
1223

1324
interface SelectOptionType {
1425
label: string;
1526
value: string;
1627
}
17-
const adminOptions: MultiValue<SelectOptionType> = [
18-
{ label: "Hello", value: "123" },
19-
{ label: "Hello2", value: "456" },
20-
];
28+
const [adminOptions, setAdminOptions] = useState<
29+
MultiValue<SelectOptionType>
30+
>([]);
31+
32+
useEffect(() => {
33+
fetchAllUsers().then((allUsers) => {
34+
setAdminOptions(
35+
allUsers.payload
36+
.filter((user: User) => user.role === "user")
37+
.map((user: User) => ({ label: user.name, value: user.uid })),
38+
);
39+
});
40+
}, [api, contextHolder]);
2141

2242
const handleSelectChange = (
2343
selectedOptions: MultiValue<SelectOptionType>,
@@ -35,6 +55,7 @@ const AdminPortalPage = () => {
3555

3656
return (
3757
<main className="mt-12 flex flex-col items-center justify-center">
58+
{contextHolder}
3859
<h1 className="mb-2 block text-5xl font-bold text-white underline">
3960
Admin Portal
4061
</h1>
@@ -58,7 +79,28 @@ const AdminPortalPage = () => {
5879
classNamePrefix="select"
5980
/>
6081
</section>
61-
<Button className="btn-accent" onClick={() => "todo"}>
82+
<Button
83+
className="btn-accent"
84+
onClick={() => {
85+
promoteToAdmin(selectedQnType.map((option) => option.value))
86+
.then((res) => {
87+
if (res.statusMessage.type.toLowerCase() === "success") {
88+
api.success({
89+
type: "success",
90+
content: "Successfully updated profile!",
91+
});
92+
} else {
93+
api.error({
94+
type: "error",
95+
content: "Failed to update profile :(",
96+
});
97+
}
98+
})
99+
.then(() => {
100+
setSelectedQnType([]);
101+
});
102+
}}
103+
>
62104
Grant Access
63105
</Button>
64106
</div>

frontend/src/app/api/index.ts

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,60 @@ export const fetchQuestionDescriptionUrl = async (qnId: string) => {
5353
);
5454
};
5555

56+
export const fetchAllQuestionsDoneByUser = async () => {
57+
const { payload } = (await FetchAuth.fetch(`${API_URL}/users/activity/`).then(
58+
(res) => {
59+
res.json();
60+
},
61+
)) as any;
62+
const questionIds = payload.join("-");
63+
// console.log({ res });
64+
return await FetchAuth.fetch(
65+
`${API_URL}/questions/group/${questionIds}`,
66+
).then((res) => {
67+
res.json();
68+
});
69+
};
70+
5671
export const fetchAllQuestionsUrl = async () => {
5772
return await FetchAuth.fetch(`${API_URL}/questions/`).then((res) =>
5873
res.json(),
5974
);
6075
};
6176

77+
export const completeQuestion = async (questionId: string) => {
78+
return await FetchAuth.fetch(`${API_URL}/users/activity/`, {
79+
method: "POST",
80+
headers: {
81+
"Content-Type": "application/json",
82+
},
83+
body: JSON.stringify({
84+
questionId,
85+
}),
86+
}).then((res) => res.json());
87+
};
88+
89+
export const promoteToAdmin = async (userId: string[]) => {
90+
return await FetchAuth.fetch(`${API_URL}/users/admin/update`, {
91+
method: "POST",
92+
headers: {
93+
"Content-Type": "application/json",
94+
},
95+
body: JSON.stringify({
96+
role: "admin",
97+
uids: userId,
98+
}),
99+
}).then((res) => res.json());
100+
};
101+
102+
export const fetchAllUsers = async () => {
103+
return await FetchAuth.fetch(`${API_URL}/users/admin/profiles`).then((res) =>
104+
res.json(),
105+
);
106+
};
107+
62108
export const fetchIsAdmin = async () => {
63-
return await FetchAuth.fetch(`${API_URL}/users/admin`)
109+
return await FetchAuth.fetch(`${API_URL}/users/admin/profiles`)
64110
.then((res) => res.json())
65111
.then((res) => {
66112
return res.statusMessage.type.toLowerCase() === "success";
@@ -95,7 +141,10 @@ export const deleteQuestionUrl = async (questionId: string) => {
95141

96142
interface ProfileResponse {
97143
payload: Profile;
98-
statusMessage: string;
144+
statusMessage: {
145+
type: "success" | "error";
146+
message: string;
147+
};
99148
}
100149

101150
export async function fetchProfileUrl(): Promise<ProfileResponse> {

frontend/src/app/components/matching/MatchingPage.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import QueueButton from "../button/QueueButton";
44
import { QuestionType } from "../../admin/question/page";
55
import { atom, useAtom } from "jotai";
66
import { innkeeperWriteAtom } from "@/libs/room-jotai";
7+
import { fetchAllQuestionsDoneByUser } from "@/app/api";
8+
import { useQuery } from "@tanstack/react-query";
9+
import { Skeleton, Table } from "antd";
710

811
const sendMatchRequestAtom = atom(
912
null,
@@ -91,8 +94,18 @@ const MatchingPage = () => {
9194
},
9295
];
9396

97+
const {
98+
data: allQuestions,
99+
isLoading: allQuestionsLoading,
100+
refetch: refetchAllQuestions,
101+
} = useQuery(["activityQuestions"], () => {
102+
return fetchAllQuestionsDoneByUser();
103+
});
104+
105+
console.log({ allQuestions });
106+
94107
return (
95-
<main className="flex h-full items-center justify-center">
108+
<main className="flex h-full flex-col items-center justify-center">
96109
<section className="flex items-center gap-4">
97110
<label>
98111
<span>Difficulty Setting:</span>
@@ -128,6 +141,18 @@ const MatchingPage = () => {
128141
</div>
129142
<QueueButton enterQueue={() => sendMatchRequest(difficulty)} />
130143
</section>
144+
<div className="m-7">
145+
<h1 className="mb-2 block text-5xl font-bold text-white underline">
146+
Completed Questions
147+
</h1>
148+
<Table
149+
className="mt-4"
150+
bordered
151+
columns={activityTableColumns}
152+
dataSource={allQuestions}
153+
pagination={{ position: ["bottomCenter"] }}
154+
/>
155+
</div>
131156
</main>
132157
);
133158
};

frontend/src/app/components/modal/AddQuestionModal.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import topicsOptions from "@/app/admin/questionTypeData";
77
import Button from "../button/Button";
88
import { QuestionType } from "@/app/admin/question/page";
99
import PreviewModalButton from "./PreviewModalButton";
10-
import QuestionModal from "./QuestionModal";
1110

1211
export interface SelectOptionType {
1312
label: string;

frontend/src/app/components/modal/EditQuestionModal.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
22
import Select, { MultiValue } from "react-select";
33
import { message } from "antd";
44
import { useMutation } from "@tanstack/react-query";
5-
import { createQuestionUrl } from "@/app/api";
5+
import { createQuestionUrl, updateQuestionUrl } from "@/app/api";
66
import topicsOptions from "@/app/admin/questionTypeData";
77
import { QuestionType } from "@/app/admin/question/page";
88

@@ -57,8 +57,8 @@ const EditQuestionModal = ({
5757
}
5858
};
5959

60-
const createQuestionMutation = useMutation(
61-
async (newQuestion: QuestionType) => createQuestionUrl(newQuestion),
60+
const editQuestionMutation = useMutation(
61+
async (newQuestion: QuestionType) => updateQuestionUrl(newQuestion),
6262
{
6363
onSuccess: () => {
6464
closeModal("edit_modal");
@@ -88,14 +88,14 @@ const EditQuestionModal = ({
8888
(formElements.namedItem("title") as HTMLInputElement)?.value || "";
8989

9090
const submissionData = {
91+
_id: question?._id ?? "",
9192
title: titleValue,
9293
difficulty: difficulty,
9394
tags: selectedTypes,
9495
description: e.currentTarget.description.value,
9596
};
9697

97-
console.log("Form Submission Data:", submissionData);
98-
createQuestionMutation.mutate(submissionData);
98+
editQuestionMutation.mutate(submissionData);
9999
};
100100

101101
return (

frontend/src/app/components/status-bar/StatusBar.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
import { executeCode } from "@/app/api";
1+
import { completeQuestion, executeCode } from "@/app/api";
22
import { UserState } from "@/libs/innkeeper-api-types";
33
import {
44
codeLangAtom,
55
codeMirrorValueAtom,
66
innkeeperWriteAtom,
77
isQuestionModalOpenAtom,
8+
questionIdAtom,
89
resultAtom,
910
userStatesAtom,
1011
} from "@/libs/room-jotai";
1112
import { atom, useAtomValue, useSetAtom } from "jotai";
1213
import Button from "../button/Button";
14+
import { message } from "antd";
1315
import UserStateBadge from "./UserStatusBadge";
1416

1517
interface StatusBarProps {
@@ -30,19 +32,41 @@ const triggerExitRoomRequestAtom = atom(null, (get, set) => {
3032
const StatusBar = ({ exitMethod }: StatusBarProps) => {
3133
const code = useAtomValue(codeMirrorValueAtom);
3234
const userStates = useAtomValue(userStatesAtom);
35+
const questionId = useAtomValue(questionIdAtom);
3336
const callExecution = useSetAtom(triggerExecutionRequestAtom);
3437
const callExitRoom = useSetAtom(triggerExitRoomRequestAtom);
3538
const setQuestionModalOpen = useSetAtom(isQuestionModalOpenAtom);
39+
const [api, contextHolder] = message.useMessage();
3640

3741
return (
3842
<footer className="fixed bottom-0 left-0 flex w-[100svw] items-center justify-between border-black bg-primary px-4 py-2 shadow-sm lg:static lg:w-full lg:px-12">
43+
{contextHolder}
3944
<div className="flex gap-4">
4045
{userStates &&
4146
userStates.map((userState: UserState) => (
4247
<UserStateBadge userState={userState} key={userState.userId} />
4348
))}
4449
</div>
4550
<div className="flex items-center gap-4">
51+
<Button
52+
className="btn-outline btn-sm"
53+
onClick={() =>
54+
questionId &&
55+
completeQuestion(questionId).then((res) =>
56+
res.statusMessage.type.toLowerCase() === "success"
57+
? api.success({
58+
type: "success",
59+
content: "Successfully completed question!",
60+
})
61+
: api.error({
62+
type: "error",
63+
content: "Failure completing question",
64+
}),
65+
)
66+
}
67+
children={<span>Mark As Complete</span>}
68+
disabled={!questionId}
69+
/>
4670
<Button
4771
className="btn-success btn-sm"
4872
onClick={() => setQuestionModalOpen(true)}

frontend/src/app/settings/page.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { BiUserCircle } from "@react-icons/all-files/bi/BiUserCircle";
44
import { fetchProfileUrl, updateProfileUrl } from "../api";
55
import useLogin from "../hooks/useLogin";
66
import useAdmin from "../hooks/useAdmin";
7+
import { message } from "antd";
78

89
const SettingPage = () => {
910
const user = useLogin();
@@ -20,6 +21,8 @@ const SettingPage = () => {
2021
// setName(user?.reloadUserInfo?.displayName ?? null);
2122
// }, [user]);
2223

24+
const [api, contextHolder] = message.useMessage();
25+
2326
const [name, setName] = useState<string>("");
2427
const [preferredLang, setPreferredLang] = useState<string | null>(null);
2528
const [profileImageUrl, setProfileImageUrl] = useState<string | null>(null);
@@ -35,15 +38,24 @@ const SettingPage = () => {
3538

3639
const onFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
3740
e.preventDefault();
38-
//TODO: submit logic
3941
updateProfileUrl(name, preferredLang, selectedImage).then((res) => {
40-
// setProfile(res.payload);
41-
console.log(res);
42+
if (res.statusMessage.type.toLowerCase() === "success") {
43+
api.success({
44+
type: "success",
45+
content: "Successfully updated profile!",
46+
});
47+
} else {
48+
api.error({
49+
type: "error",
50+
content: "Failed to update profile :(",
51+
});
52+
}
4253
});
4354
};
4455

4556
return (
4657
<main className="flex flex-col items-center gap-4 p-12">
58+
{contextHolder}
4759
<h1 className="text-5xl font-bold text-white underline">User Profile</h1>
4860
<form
4961
className="flex flex-col justify-center gap-8 bg-secondary p-12"

0 commit comments

Comments
 (0)