Skip to content

Commit 0b5e5de

Browse files
Merge pull request #40 from CS3219-AY2425S1/frontend-websocket
Frontend Websocket
2 parents f8e0cda + c0e2088 commit 0b5e5de

File tree

20 files changed

+412
-78
lines changed

20 files changed

+412
-78
lines changed

.github/workflows/test.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
branches:
66
- main
77
- staging
8+
- frontend-websocket-test
89
pull_request:
910
branches:
1011
- main
@@ -27,6 +28,7 @@ jobs:
2728
env:
2829
QUESTION_SERVICE_URL: ${{ vars.QUESTION_SERVICE_URL }}
2930
USER_SERVICE_URL: ${{ vars.USER_SERVICE_URL }}
31+
MATCHING_SERVICE_URL: ${{ vars.MATCHING_SERVICE_URL }}
3032
JWT_SECRET: ${{ secrets.JWT_SECRET }}
3133
FIREBASE_CREDENTIAL_PATH: ${{ vars.QUESTION_SERVICE_FIREBASE_CREDENTIAL_PATH }}
3234
DB_CLOUD_URI: ${{ secrets.USER_SERVICE_DB_CLOUD_URI }}
@@ -35,6 +37,7 @@ jobs:
3537
cd ./apps/frontend
3638
echo "NEXT_PUBLIC_QUESTION_SERVICE_URL=$QUESTION_SERVICE_URL" >> .env
3739
echo "NEXT_PUBLIC_USER_SERVICE_URL=$USER_SERVICE_URL" >> .env
40+
echo "NEXT_PUBLIC_MATCHING_SERVICE_URL=$MATCHING_SERVICE_URL" >> .env
3841
3942
cd ../question-service
4043
echo "FIREBASE_CREDENTIAL_PATH=$FIREBASE_CREDENTIAL_PATH" >> .env

apps/frontend/.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# URL endpoints of the services
22
NEXT_PUBLIC_QUESTION_SERVICE_URL="http://localhost:8080/"
3-
NEXT_PUBLIC_USER_SERVICE_URL="http://localhost:3001/"
3+
NEXT_PUBLIC_USER_SERVICE_URL="http://localhost:3001/"
4+
NEXT_PUBLIC_MATCHING_SERVICE_URL="ws://localhost:8081/match"

apps/frontend/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Then, follow the `.env.example` file and create a `.env` file in the current dir
2525
```bash
2626
NEXT_PUBLIC_QUESTION_SERVICE_URL="http://localhost:8080"
2727
NEXT_PUBLIC_USER_SERVICE_URL="http://localhost:3001/"
28+
NEXT_PUBLIC_MATCHING_SERVICE_URL="wss://localhost:8081"
2829
```
2930

3031
First, run the development server:

apps/frontend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
"next": "14.2.13",
1717
"react": "^18.2.0",
1818
"react-dom": "^18.2.0",
19+
"react-timer-hook": "^3.0.7",
20+
"react-use-websocket": "^4.9.0",
1921
"sass": "^1.79.2",
2022
"typeface-montserrat": "^1.1.13"
2123
},

apps/frontend/pnpm-lock.yaml

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/frontend/src/app/layout.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from "react";
22
import { AntdRegistry } from "@ant-design/nextjs-registry";
33
import { ConfigProvider } from "antd";
4+
import WebSocketProvider from "@/components/WebSocketProvider/websocketprovider";
45

56
const RootLayout = ({
67
children,
@@ -23,7 +24,11 @@ const RootLayout = ({
2324
},
2425
}}
2526
>
26-
<AntdRegistry>{children}</AntdRegistry>
27+
<AntdRegistry>
28+
<WebSocketProvider>
29+
{children}
30+
</WebSocketProvider>
31+
</AntdRegistry>
2732
</ConfigProvider>
2833
</body>
2934
</html>

apps/frontend/src/app/matching/MatchingModal.tsx

Lines changed: 66 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,66 +6,98 @@ import 'typeface-montserrat';
66
import './styles.scss';
77
import FindMatchContent from './modalContent/FindMatchContent';
88
import MatchingInProgressContent from './modalContent/MatchingInProgressContent';
9-
import MatchFound from './modalContent/MatchFoundContent';
9+
import MatchFoundContent from './modalContent/MatchFoundContent';
1010
import JoinedMatchContent from './modalContent/JoinedMatchContent';
1111
import MatchNotFoundContent from './modalContent/MatchNotFoundContent';
1212
import MatchCancelledContent from './modalContent/MatchCancelledContent';
13+
import useMatching from '../services/use-matching';
1314

1415
interface MatchingModalProps {
1516
isOpen: boolean;
16-
onClose: () => void;
17+
close: () => void;
1718
}
1819

19-
const MatchingModal: React.FC<MatchingModalProps> = ({ isOpen, onClose }) => {
20-
// TODO: placehoder for now, to be replaced my useContext
21-
const [matchingState, setMatchingState] = useState('finding');
20+
const MatchingModal: React.FC<MatchingModalProps> = ({ isOpen, close: _close }) => {
21+
const matchingState = useMatching();
22+
const [closedType, setClosedType] = useState<"finding" | "cancelled" | "joined">("finding");
23+
const [timeoutAfter, setTimeoutAfter] = useState<number>(9999);
24+
const isClosable = ["timeout", "closed"].includes(matchingState.state);
2225

23-
// TODO: remove this after testing
24-
useEffect(() => {
25-
// Uncomment the following lines to test the different matching states
26-
// setMatchingState('finding');
27-
// setMatchingState('matching');
28-
// setMatchingState('found');
29-
// setMatchingState('joined');
30-
// setMatchingState('notFound');
31-
// setMatchingState('cancelled');
32-
}, []);
33-
34-
// TODO: modify by using matchingState via useContext
35-
const isClosableMatchingState = () => {
36-
return matchingState === 'finding' || matchingState === 'notFound' || matchingState === 'cancelled';
37-
};
26+
function close() {
27+
// clean up matching and closedType State
28+
if (matchingState.state === "timeout") {
29+
matchingState.ok();
30+
}
31+
setClosedType("finding");
32+
_close();
33+
}
3834

3935
const renderModalContent = () => {
40-
switch (matchingState) {
41-
case 'finding':
42-
return <FindMatchContent/>;
36+
switch (matchingState.state) {
37+
case 'closed':
38+
switch (closedType) {
39+
case "finding":
40+
return <FindMatchContent beginMatch={matchingState.start}/>;
41+
case "cancelled":
42+
return <MatchCancelledContent
43+
reselect={() => {
44+
setClosedType("finding");
45+
}}
46+
retry={() => {}}
47+
canceledIn={timeoutAfter}
48+
/>;
49+
case "joined":
50+
return <JoinedMatchContent cancel={() => {
51+
setClosedType("cancelled");
52+
}}/>;
53+
}
4354
case 'matching':
44-
return <MatchingInProgressContent />;
55+
return <MatchingInProgressContent
56+
cancelMatch={(timeoutAfter: number) => {
57+
setClosedType("cancelled");
58+
setTimeoutAfter(timeoutAfter);
59+
matchingState.cancel();
60+
}}
61+
timeout={(timeoutAfter: number) => {
62+
matchingState.timeout()
63+
setTimeoutAfter(timeoutAfter);
64+
}}
65+
/>;
66+
case 'cancelling':
67+
return <MatchingInProgressContent cancelMatch={() => {}} timeout={() => {}}/>;
68+
case 'starting':
69+
return <FindMatchContent beginMatch={() => {}}/>
4570
case 'found':
46-
return <MatchFound />;
47-
case 'joined':
48-
return <JoinedMatchContent />;
49-
case 'notFound':
50-
return <MatchNotFoundContent />;
51-
case 'cancelled':
52-
return <MatchCancelledContent />;
71+
return <MatchFoundContent
72+
cancel={() => {
73+
matchingState.ok();
74+
setClosedType("cancelled");
75+
}}
76+
join={() => {
77+
matchingState.ok();
78+
setClosedType("joined");
79+
}}
80+
name1={matchingState.info.myName}
81+
name2={matchingState.info.partnerName}
82+
/>
83+
case 'timeout':
84+
return <MatchNotFoundContent reselect={matchingState.ok} retry={() => {}} timedOutIn={10}/>;
5385
default:
5486
throw new Error('Invalid matching state.');
5587
}
5688
};
5789

5890
return (
5991
<Modal open={isOpen}
60-
onCancel={onClose}
92+
onCancel={close}
6193
footer={null}
6294
closable={false}
6395
maskClosable={false}
6496
className="modal"
6597
>
6698
{renderModalContent()}
67-
{isClosableMatchingState() && (
68-
<button className="close-button" onClick={onClose}>Close</button>
99+
{isClosable && (
100+
<button className="close-button" onClick={close}>Close</button>
69101
)}
70102
</Modal>
71103
)

apps/frontend/src/app/matching/modalContent/FindMatchContent.tsx

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {
1111
import type { SelectProps } from 'antd';
1212
import 'typeface-montserrat';
1313
import './styles.scss';
14-
import { handleFindMatch } from '../handlers';
14+
import { ValidateUser } from "@/app/services/user"
15+
import { type MatchRequestParams } from '@/app/services/use-matching';
1516

1617
interface DifficultySelectorProps {
1718
className?: string;
@@ -25,9 +26,14 @@ interface TopicSelectorProps {
2526
onChange: (topics: string[]) => void;
2627
}
2728

28-
const FindMatchContent: React.FC = () => {
29+
interface Props {
30+
beginMatch(request: MatchRequestParams): void
31+
}
32+
33+
const FindMatchContent: React.FC<Props> = ({ beginMatch }) => {
2934
const [selectedDifficulties, setSelectedDifficulties] = useState<string[]>([]);
3035
const [selectedTopics, setSelectedTopics] = useState<string[]>([]);
36+
const [isLoading, setIsLoading] = useState(false);
3137

3238
const handleDifficultyChange = (difficulties: string[]) => {
3339
setSelectedDifficulties(difficulties);
@@ -55,7 +61,18 @@ const FindMatchContent: React.FC = () => {
5561
/>
5662
</div>
5763
<button className="find-match-button"
58-
onClick={handleFindMatch}
64+
onClick={async () => {
65+
setIsLoading(true);
66+
const user = await ValidateUser();
67+
beginMatch({
68+
email: user.data.email,
69+
username: user.data.username,
70+
type: "match_request",
71+
difficulties: selectedDifficulties,
72+
topics: selectedTopics,
73+
})
74+
}}
75+
disabled={isLoading}
5976
>
6077
Find Match
6178
</button>
@@ -77,8 +94,8 @@ const DifficultySelector: React.FC<DifficultySelectorProps> = ({ selectedDifficu
7794
<Tag.CheckableTag
7895
className={`difficulty-tag ${difficultyOption.value}-tag`}
7996
key={difficultyOption.value}
80-
checked={selectedDifficulties.includes(difficultyOption.value)}
81-
onChange={() => handleChange(difficultyOption.value)}
97+
checked={selectedDifficulties.includes(difficultyOption.label)}
98+
onChange={() => handleChange(difficultyOption.label)}
8299
>
83100
{difficultyOption.label}
84101
</Tag.CheckableTag>
@@ -90,11 +107,8 @@ const DifficultySelector: React.FC<DifficultySelectorProps> = ({ selectedDifficu
90107
const TopicSelector: React.FC<TopicSelectorProps> = ({ selectedTopics, onChange}) => {
91108
const topicOptions: SelectProps[] = CategoriesOption;
92109

93-
const handleChange = (topic: string) => {
94-
const newSelectedTopics = selectedTopics.includes(topic)
95-
? selectedTopics.filter(selectedTopic => selectedTopic !== topic)
96-
: [...selectedTopics, topic];
97-
onChange(newSelectedTopics);
110+
const handleChange = (topics: string[]) => {
111+
onChange(topics);
98112
}
99113

100114
return (

apps/frontend/src/app/matching/modalContent/JoinedMatchContent.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import './styles.scss';
1010
import { handleCancelMatch } from '../handlers';
1111
import { formatTime } from '@/utils/DateTime';
1212

13-
const JoinedMatchContent: React.FC = () => {
13+
14+
interface Props {
15+
cancel(): void
16+
}
17+
18+
const JoinedMatchContent: React.FC<Props> = ({cancel}) => {
1419
const matchAlreadyJoined = () => {
1520
throw new Error('Match already joined.');
1621
}
@@ -36,12 +41,11 @@ const JoinedMatchContent: React.FC = () => {
3641
</div>
3742
<button className="joined-match-deactivated-button"
3843
disabled
39-
onClick={() => matchAlreadyJoined()}
4044
>
4145
Joined
4246
</button>
4347
<button className="cancel-match-button"
44-
onClick={() => handleCancelMatch()}
48+
onClick={cancel}
4549
>
4650
Cancel
4751
</button>

apps/frontend/src/app/matching/modalContent/MatchCancelledContent.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ import './styles.scss';
44
import { handleReselectMatchOptions, handleRetryMatch } from '../handlers';
55
import { formatTime } from '@/utils/DateTime';
66

7-
const MatchCancelledContent: React.FC = () => {
7+
interface Props {
8+
retry(): void,
9+
reselect(): void,
10+
canceledIn: number,
11+
}
12+
13+
const MatchCancelledContent: React.FC<Props> = ({retry, reselect, canceledIn}) => {
814
return (
915
<div className="match-cancelled-content">
1016
<div className="cancel-icon-container">
@@ -20,15 +26,15 @@ const MatchCancelledContent: React.FC = () => {
2026
</div>
2127
<div className="match-status-label">Match Cancelled!</div>
2228
<div className="match-status-message">
23-
Your match request has been cancelled after waiting {formatTime(83)}
29+
Your match request has been cancelled after waiting {formatTime(canceledIn)}
2430
</div>
25-
<button className="retry-match-button"
26-
onClick={() => handleRetryMatch()}
31+
{/* <button className="retry-match-button"
32+
onClick={retry}
2733
>
2834
Retry
29-
</button>
35+
</button> */}
3036
<button className="reselect-match-options-button"
31-
onClick={() => handleReselectMatchOptions()}
37+
onClick={reselect}
3238
>
3339
Reselect Options
3440
</button>

0 commit comments

Comments
 (0)