Skip to content

Commit 3fc4f2c

Browse files
committed
Merge branch 'main' into enhancement/api-gateway
2 parents 92b7f62 + ff1ceae commit 3fc4f2c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+2322
-662
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/auth/auth-context.tsx

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

3-
import { AuthType, userServiceUri } from "@/lib/api-uri";
3+
import { AuthType, userServiceUri } from "@/lib/api/api-uri";
44
import { User, UserSchema } from "@/lib/schemas/user-schema";
55
import { useRouter } from "next/navigation";
66
import {
@@ -78,7 +78,16 @@ const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
7878
);
7979

8080
if (!response.ok) {
81-
throw new Error("Not OK");
81+
switch (response.status) {
82+
case 400:
83+
throw new Error("Email and/or password is missing.");
84+
case 401:
85+
throw new Error("Invalid email or password.");
86+
case 500:
87+
throw new Error("Internal server error. Please try again later.");
88+
default:
89+
throw new Error("Unexpected error occurred.");
90+
}
8291
}
8392

8493
const resJson = await response.json();

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import {
1111
import { Button } from "../ui/button";
1212
import { Input } from "../ui/input";
1313
import { Label } from "../ui/label";
14-
import { updateUser } from "@/lib/update-user";
14+
import { updateUser } from "@/lib/api/user-service/update-user";
1515
import { useAuth } from "@/app/auth/auth-context";
1616
import { useToast } from "@/components/hooks/use-toast";
1717
import { Switch } from "../ui/switch";
18-
import { updateUserPrivilege } from "@/lib/update-user-privilege";
18+
import { updateUserPrivilege } from "@/lib/api/user-service/update-user-privilege";
1919
import { User } from "@/lib/schemas/user-schema";
2020

2121
interface AdminEditUserModalProps extends React.HTMLProps<HTMLDivElement> {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import AdminEditUserModal from "@/components/admin-user-management/admin-edit-us
1717
import DeleteAccountModal from "@/components/common/delete-account-modal";
1818
import { PencilIcon, Trash2Icon } from "lucide-react";
1919
import { User, UserArraySchema } from "@/lib/schemas/user-schema";
20-
import { AuthType, userServiceUri } from "@/lib/api-uri";
20+
import { AuthType, userServiceUri } from "@/lib/api/api-uri";
2121

2222
const fetcher = async (url: string): Promise<User[]> => {
2323
const token = localStorage.getItem("jwtToken");

frontend/components/auth/login-form.tsx

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,37 @@ export function LoginForm() {
4141
description: "Login Failed.",
4242
});
4343
}
44-
} catch (err) {
45-
toast({
46-
title: "Error",
47-
variant: "destructive",
48-
description: "Login Failed.",
49-
});
44+
} catch (err: unknown) {
45+
if (err instanceof Error) {
46+
let description_text = "";
47+
switch (err.message) {
48+
case "Email and/or password is missing.":
49+
description_text = "Please provide both email and password.";
50+
break;
51+
case "Invalid email or password.":
52+
description_text = "Username or password is incorrect.";
53+
break;
54+
case "Internal server error. Please try again later.":
55+
description_text =
56+
"There was an issue with the server. Please try again later.";
57+
break;
58+
default:
59+
description_text =
60+
"An unexpected error occurred. Please try again.";
61+
break;
62+
}
63+
toast({
64+
title: "Error",
65+
variant: "destructive",
66+
description: description_text,
67+
});
68+
} else {
69+
toast({
70+
title: "Error",
71+
variant: "destructive",
72+
description: "An unexpected error occurred. Please try again.",
73+
});
74+
}
5075
}
5176
};
5277

frontend/components/auth/reset-password-form.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { useState } from "react";
44
import { useRouter } from "next/navigation";
5-
import { resetPassword } from "@/lib/reset-password";
5+
import { resetPassword } from "@/lib/api/user-service/reset-password";
66
import { isPasswordComplex } from "@/lib/password";
77
import { useToast } from "@/components/hooks/use-toast";
88

frontend/components/auth/sign-up-form.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Link from "next/link";
44
import { useState } from "react";
55
import { useRouter } from "next/navigation";
66
import { toast } from "@/components/hooks/use-toast";
7-
import { signUp } from "@/lib/signup";
7+
import { signUp } from "@/lib/api/user-service/signup";
88
import { isPasswordComplex } from "@/lib/password";
99

1010
import { Button } from "@/components/ui/button";

frontend/components/forget-password.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { Label } from "@/components/ui/label";
1515
import { Button } from "@/components/ui/button";
1616
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
1717
import { AlertCircle } from "lucide-react";
18-
import { AuthType, userServiceUri } from "@/lib/api-uri";
18+
import { AuthType, userServiceUri } from "@/lib/api/api-uri";
1919

2020
const ForgetPassword: React.FC = () => {
2121
const [email, setEmail] = useState("");
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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+
import { useAuth } from "@/app/auth/auth-context";
8+
import { joinMatchQueue } from "@/lib/join-match-queue";
9+
import { leaveMatchQueue } from "@/lib/leave-match-queue";
10+
import { subscribeMatch } from "@/lib/subscribe-match";
11+
12+
export default function FindMatch() {
13+
const [selectedDifficulty, setSelectedDifficulty] = useState<string>("");
14+
const [selectedTopic, setSelectedTopic] = useState<string>("");
15+
const [isSearching, setIsSearching] = useState<boolean>(false);
16+
const [waitTime, setWaitTime] = useState<number>(0);
17+
const { toast } = useToast();
18+
const auth = useAuth();
19+
20+
const waitTimeout = 60000;
21+
22+
useEffect(() => {
23+
let interval: NodeJS.Timeout | undefined;
24+
if (isSearching) {
25+
interval = setInterval(() => {
26+
setWaitTime((prevTime) => prevTime + 1);
27+
}, 1000);
28+
} else {
29+
setWaitTime(0);
30+
}
31+
32+
return () => {
33+
clearInterval(interval);
34+
};
35+
}, [isSearching]);
36+
37+
const handleSearch = async () => {
38+
if (!selectedDifficulty || !selectedTopic) {
39+
toast({
40+
title: "Invalid Selection",
41+
description: "Please select both a difficulty level and a topic",
42+
variant: "destructive",
43+
});
44+
return;
45+
}
46+
47+
if (!auth || !auth.token) {
48+
toast({
49+
title: "Access denied",
50+
description: "No authentication token found",
51+
variant: "destructive",
52+
});
53+
return;
54+
}
55+
56+
if (!auth.user) {
57+
toast({
58+
title: "Access denied",
59+
description: "Not logged in",
60+
variant: "destructive",
61+
});
62+
return;
63+
}
64+
65+
const response = await joinMatchQueue(
66+
auth.token,
67+
auth?.user?.id,
68+
selectedTopic,
69+
selectedDifficulty
70+
);
71+
switch (response.status) {
72+
case 201:
73+
toast({
74+
title: "Matched",
75+
description: "Successfully matched",
76+
variant: "success",
77+
});
78+
return;
79+
case 202:
80+
case 304:
81+
setIsSearching(true);
82+
const ws = await subscribeMatch(
83+
auth?.user.id,
84+
selectedTopic,
85+
selectedDifficulty
86+
);
87+
const queueTimeout = setTimeout(() => {
88+
handleCancel(true);
89+
}, waitTimeout);
90+
ws.onmessage = () => {
91+
setIsSearching(false);
92+
clearTimeout(queueTimeout);
93+
toast({
94+
title: "Matched",
95+
description: "Successfully matched",
96+
variant: "success",
97+
});
98+
ws.onclose = () => null;
99+
};
100+
ws.onclose = () => {
101+
setIsSearching(false);
102+
clearTimeout(queueTimeout);
103+
toast({
104+
title: "Matching Stopped",
105+
description: "Matching has been stopped",
106+
variant: "destructive",
107+
});
108+
};
109+
return;
110+
default:
111+
toast({
112+
title: "Unknown Error",
113+
description: "An unexpected error has occured",
114+
variant: "destructive",
115+
});
116+
return;
117+
}
118+
};
119+
120+
const handleCancel = async (timedOut: boolean) => {
121+
if (!selectedDifficulty || !selectedTopic) {
122+
toast({
123+
title: "Invalid Selection",
124+
description: "Please select both a difficulty level and a topic",
125+
variant: "destructive",
126+
});
127+
return;
128+
}
129+
130+
if (!auth || !auth.token) {
131+
toast({
132+
title: "Access denied",
133+
description: "No authentication token found",
134+
variant: "destructive",
135+
});
136+
return;
137+
}
138+
139+
if (!auth.user) {
140+
toast({
141+
title: "Access denied",
142+
description: "Not logged in",
143+
variant: "destructive",
144+
});
145+
return;
146+
}
147+
148+
const response = await leaveMatchQueue(
149+
auth.token,
150+
auth.user?.id,
151+
selectedTopic,
152+
selectedDifficulty
153+
);
154+
switch (response.status) {
155+
case 200:
156+
setIsSearching(false);
157+
setWaitTime(0);
158+
if (timedOut) {
159+
toast({
160+
title: "Timed Out",
161+
description: "Matching has been stopped",
162+
variant: "destructive",
163+
});
164+
} else {
165+
toast({
166+
title: "Matching Stopped",
167+
description: "Matching has been stopped",
168+
variant: "destructive",
169+
});
170+
}
171+
return;
172+
default:
173+
toast({
174+
title: "Unknown Error",
175+
description: "An unexpected error has occured",
176+
variant: "destructive",
177+
});
178+
return;
179+
}
180+
};
181+
182+
return (
183+
<div className="container mx-auto p-4">
184+
<MatchForm
185+
selectedDifficulty={selectedDifficulty}
186+
setSelectedDifficulty={setSelectedDifficulty}
187+
selectedTopic={selectedTopic}
188+
setSelectedTopic={setSelectedTopic}
189+
handleSearch={handleSearch}
190+
isSearching={isSearching}
191+
handleCancel={() => handleCancel(false)}
192+
/>
193+
194+
{isSearching && <SearchProgress waitTime={waitTime} />}
195+
196+
{!isSearching && (selectedDifficulty || selectedTopic) && (
197+
<SelectionSummary
198+
selectedDifficulty={selectedDifficulty}
199+
selectedTopic={selectedTopic}
200+
/>
201+
)}
202+
</div>
203+
);
204+
}

0 commit comments

Comments
 (0)