Skip to content

Commit dc56924

Browse files
committed
Merge branch 'enhancement/question-cat-enum' of github.com:CS3219-AY2425S1/cs3219-ay2425s1-project-g50 into enhancement/question-cat-enum
2 parents f412b3b + d423ffa commit dc56924

File tree

15 files changed

+387
-220
lines changed

15 files changed

+387
-220
lines changed

frontend/app/app/matching/page.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import AuthPageWrapper from "@/components/auth/auth-page-wrapper";
2+
import FindMatch from "@/components/matching/find-match";
3+
import { Suspense } from "react";
4+
5+
export default function MatchingPage() {
6+
return (
7+
<AuthPageWrapper requireLoggedIn>
8+
<Suspense>
9+
<FindMatch />
10+
</Suspense>
11+
</AuthPageWrapper>
12+
);
13+
}

frontend/app/app/user-settings/[user_id]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default function UserSettingsPage({
88
}) {
99
return (
1010
<AuthPageWrapper requireLoggedIn userId={params.user_id}>
11-
<UserSettings userId={params.user_id} />;
11+
<UserSettings userId={params.user_id} />
1212
</AuthPageWrapper>
1313
);
1414
}

frontend/components/admin-user-management/admin-user-management.tsx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
} from "@/components/ui/table";
1515
import LoadingScreen from "@/components/common/loading-screen";
1616
import AdminEditUserModal from "@/components/admin-user-management/admin-edit-user-modal";
17+
import DeleteAccountModal from "@/components/common/delete-account-modal";
1718
import { PencilIcon, Trash2Icon } from "lucide-react";
1819
import { User, UserArraySchema } from "@/lib/schemas/user-schema";
1920
import { userServiceUri } from "@/lib/api/api-uri";
@@ -51,25 +52,33 @@ export default function AdminUserManagement() {
5152
const [users, setUsers] = useState<User[]>([]);
5253
const [showModal, setShowModal] = useState<boolean>(false);
5354
const [selectedUser, setSelectedUser] = useState<User>();
55+
const [showDeleteModal, setShowDeleteModal] = useState(false);
56+
const [confirmUsername, setConfirmUsername] = useState("");
57+
const [isDeleteButtonEnabled, setIsDeleteButtonEnabled] = useState(false);
5458

5559
useEffect(() => {
5660
if (data) {
5761
setUsers(data);
5862
}
5963
}, [data]);
6064

65+
// Enable delete button in the delete account modal only when the input username matches the original username
66+
useEffect(() => {
67+
setIsDeleteButtonEnabled(confirmUsername === selectedUser?.username);
68+
}, [confirmUsername, selectedUser]);
69+
6170
if (isLoading) {
6271
return <LoadingScreen />;
6372
}
6473

65-
const handleDelete = async (userId: string) => {
74+
const handleDelete = async () => {
6675
const token = auth?.token;
6776
if (!token) {
6877
throw new Error("No authentication token found");
6978
}
7079

7180
const response = await fetch(
72-
`${userServiceUri(window.location.hostname)}/users/${userId}`,
81+
`${userServiceUri(window.location.hostname)}/users/${selectedUser?.id}`,
7382
{
7483
method: "DELETE",
7584
headers: {
@@ -82,7 +91,7 @@ export default function AdminUserManagement() {
8291
throw new Error("Failed to delete user");
8392
}
8493

85-
setUsers(users.filter((user) => user.id !== userId));
94+
setUsers(users.filter((user) => user.id !== selectedUser?.id));
8695
};
8796

8897
const onUserUpdate = () => {
@@ -100,6 +109,16 @@ export default function AdminUserManagement() {
100109
user={selectedUser}
101110
onUserUpdate={onUserUpdate}
102111
/>
112+
<DeleteAccountModal
113+
showDeleteModal={showDeleteModal}
114+
originalUsername={selectedUser?.username || ""}
115+
confirmUsername={confirmUsername}
116+
setConfirmUsername={setConfirmUsername}
117+
handleDeleteAccount={handleDelete}
118+
isDeleteButtonEnabled={isDeleteButtonEnabled}
119+
setShowDeleteModal={setShowDeleteModal}
120+
isAdmin={true}
121+
/>
103122
<Table>
104123
<TableHeader>
105124
<TableRow>
@@ -130,7 +149,10 @@ export default function AdminUserManagement() {
130149
</Button>
131150
<Button
132151
variant="destructive"
133-
onClick={() => handleDelete(user.id)}
152+
onClick={() => {
153+
setSelectedUser(user);
154+
setShowDeleteModal(true);
155+
}}
134156
>
135157
<Trash2Icon className="h-4 w-4" />
136158
</Button>

frontend/components/user-settings/delete-account-modal.tsx renamed to frontend/components/common/delete-account-modal.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ interface DeleteAccountModalProps {
1717
handleDeleteAccount: () => void;
1818
isDeleteButtonEnabled: boolean;
1919
setShowDeleteModal: (show: boolean) => void;
20+
isAdmin: boolean;
2021
}
2122

2223
const DeleteAccountModal: React.FC<DeleteAccountModalProps> = ({
@@ -27,6 +28,7 @@ const DeleteAccountModal: React.FC<DeleteAccountModalProps> = ({
2728
handleDeleteAccount,
2829
isDeleteButtonEnabled,
2930
setShowDeleteModal,
31+
isAdmin,
3032
}) => {
3133
return (
3234
<>
@@ -37,10 +39,18 @@ const DeleteAccountModal: React.FC<DeleteAccountModalProps> = ({
3739
<DialogTitle>Confirm Delete Account</DialogTitle>
3840
</DialogHeader>
3941
<div className="space-y-4">
40-
<p>To confirm, please type your username ({originalUsername}):</p>
42+
{isAdmin ? (
43+
<p>
44+
To delete, please confirm the username ({originalUsername}):
45+
</p>
46+
) : (
47+
<p>
48+
To confirm, please type your username ({originalUsername}):
49+
</p>
50+
)}
4151
<Input
4252
type="text"
43-
placeholder="Enter your username"
53+
placeholder="Confirm username"
4454
value={confirmUsername}
4555
onChange={(e) => setConfirmUsername(e.target.value)}
4656
/>
@@ -57,7 +67,11 @@ const DeleteAccountModal: React.FC<DeleteAccountModalProps> = ({
5767
</Button>
5868
<Button
5969
variant="destructive"
60-
onClick={handleDeleteAccount}
70+
onClick={() => {
71+
setShowDeleteModal(false);
72+
setConfirmUsername("");
73+
handleDeleteAccount();
74+
}}
6175
disabled={!isDeleteButtonEnabled}
6276
>
6377
Delete Account
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"use client";
2+
import React, { useState, useEffect } from "react";
3+
import { MatchForm } from "@/components/matching/matching-form";
4+
import { SearchProgress } from "@/components/matching/search-progress";
5+
import { SelectionSummary } from "@/components/matching/selection-summary";
6+
import { useToast } from "@/components/hooks/use-toast";
7+
8+
export default function FindMatch() {
9+
const [selectedDifficulty, setSelectedDifficulty] = useState<string>("");
10+
const [selectedTopic, setSelectedTopic] = useState<string>("");
11+
const [isSearching, setIsSearching] = useState<boolean>(false);
12+
const [waitTime, setWaitTime] = useState<number>(0);
13+
const { toast } = useToast();
14+
15+
useEffect(() => {
16+
let interval: NodeJS.Timeout | undefined;
17+
if (isSearching) {
18+
interval = setInterval(() => {
19+
setWaitTime((prevTime) => prevTime + 1);
20+
}, 1000);
21+
} else {
22+
setWaitTime(0);
23+
}
24+
return () => clearInterval(interval);
25+
}, [isSearching]);
26+
27+
const handleSearch = () => {
28+
if (selectedDifficulty && selectedTopic) {
29+
setIsSearching(true);
30+
} else {
31+
toast({
32+
title: "Invalid Selection",
33+
description: "Please select both a difficulty level and a topic",
34+
variant: "destructive",
35+
});
36+
}
37+
};
38+
39+
const handleCancel = () => {
40+
setIsSearching(false);
41+
setWaitTime(0);
42+
};
43+
44+
return (
45+
<div className="container mx-auto p-4">
46+
<MatchForm
47+
selectedDifficulty={selectedDifficulty}
48+
setSelectedDifficulty={setSelectedDifficulty}
49+
selectedTopic={selectedTopic}
50+
setSelectedTopic={setSelectedTopic}
51+
handleSearch={handleSearch}
52+
isSearching={isSearching}
53+
handleCancel={handleCancel}
54+
/>
55+
56+
{isSearching && <SearchProgress waitTime={waitTime} />}
57+
58+
{!isSearching && (selectedDifficulty || selectedTopic) && (
59+
<SelectionSummary
60+
selectedDifficulty={selectedDifficulty}
61+
selectedTopic={selectedTopic}
62+
/>
63+
)}
64+
</div>
65+
);
66+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import React from "react";
2+
import { Button } from "@/components/ui/button";
3+
import {
4+
Card,
5+
CardContent,
6+
CardDescription,
7+
CardFooter,
8+
CardHeader,
9+
CardTitle,
10+
} from "@/components/ui/card";
11+
import {
12+
Select,
13+
SelectContent,
14+
SelectItem,
15+
SelectTrigger,
16+
SelectValue,
17+
} from "@/components/ui/select";
18+
import { Label } from "@/components/ui/label";
19+
20+
const difficulties: string[] = ["Easy", "Medium", "Hard"];
21+
const topics: string[] = [
22+
"Arrays",
23+
"Strings",
24+
"Linked Lists",
25+
"Trees",
26+
"Graphs",
27+
"Dynamic Programming",
28+
];
29+
30+
interface MatchFormProps {
31+
selectedDifficulty: string;
32+
setSelectedDifficulty: (difficulty: string) => void;
33+
selectedTopic: string;
34+
setSelectedTopic: (topic: string) => void;
35+
handleSearch: () => void;
36+
isSearching: boolean;
37+
handleCancel: () => void;
38+
}
39+
40+
export function MatchForm({
41+
selectedDifficulty,
42+
setSelectedDifficulty,
43+
selectedTopic,
44+
setSelectedTopic,
45+
handleSearch,
46+
isSearching,
47+
handleCancel,
48+
}: MatchFormProps) {
49+
return (
50+
<Card className="w-full max-w-2xl mx-auto">
51+
<CardHeader>
52+
<CardTitle>Find a Match</CardTitle>
53+
<CardDescription>
54+
Select your preferred difficulty level and topic to find a match.
55+
</CardDescription>
56+
</CardHeader>
57+
<CardContent>
58+
<div className="space-y-6">
59+
<div>
60+
<Label
61+
htmlFor="difficulty-select"
62+
className="text-lg font-medium mb-2 block"
63+
>
64+
Difficulty Level
65+
</Label>
66+
<Select
67+
value={selectedDifficulty}
68+
onValueChange={setSelectedDifficulty}
69+
>
70+
<SelectTrigger id="difficulty-select">
71+
<SelectValue placeholder="Select difficulty" />
72+
</SelectTrigger>
73+
<SelectContent>
74+
{difficulties.map((difficulty) => (
75+
<SelectItem key={difficulty} value={difficulty}>
76+
{difficulty}
77+
</SelectItem>
78+
))}
79+
</SelectContent>
80+
</Select>
81+
</div>
82+
<div>
83+
<Label
84+
htmlFor="topic-select"
85+
className="text-lg font-medium mb-2 block"
86+
>
87+
Topic
88+
</Label>
89+
<Select value={selectedTopic} onValueChange={setSelectedTopic}>
90+
<SelectTrigger id="topic-select">
91+
<SelectValue placeholder="Select topic" />
92+
</SelectTrigger>
93+
<SelectContent>
94+
{topics.map((topic) => (
95+
<SelectItem key={topic} value={topic}>
96+
{topic}
97+
</SelectItem>
98+
))}
99+
</SelectContent>
100+
</Select>
101+
</div>
102+
</div>
103+
</CardContent>
104+
<CardFooter className="flex justify-between">
105+
{!isSearching ? (
106+
<Button onClick={handleSearch}>Find Match</Button>
107+
) : (
108+
<Button variant="destructive" onClick={handleCancel}>
109+
Cancel Search
110+
</Button>
111+
)}
112+
</CardFooter>
113+
</Card>
114+
);
115+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React from "react";
2+
import {
3+
Card,
4+
CardContent,
5+
CardDescription,
6+
CardHeader,
7+
CardTitle,
8+
} from "@/components/ui/card";
9+
import { Progress } from "@/components/ui/progress";
10+
import { Clock } from "lucide-react";
11+
12+
interface SearchProgressProps {
13+
waitTime: number;
14+
}
15+
16+
export function SearchProgress({ waitTime }: SearchProgressProps) {
17+
return (
18+
<Card className="w-full max-w-2xl mx-auto mt-4">
19+
<CardHeader>
20+
<CardTitle>Searching for Match</CardTitle>
21+
<CardDescription>
22+
Please wait while we find a suitable match for you.
23+
</CardDescription>
24+
</CardHeader>
25+
<CardContent>
26+
<div className="space-y-4">
27+
<Progress className="w-full" indeterminate />
28+
<div className="flex items-center justify-between">
29+
<div className="flex items-center space-x-2">
30+
<Clock className="h-4 w-4" />
31+
<span>
32+
Wait Time: {Math.floor(waitTime / 60)}:
33+
{(waitTime % 60).toString().padStart(2, "0")}
34+
</span>
35+
</div>
36+
<span>Searching...</span>
37+
</div>
38+
</div>
39+
</CardContent>
40+
</Card>
41+
);
42+
}

0 commit comments

Comments
 (0)