Skip to content

Commit 79b3170

Browse files
committed
Integrate useMatching interface with UI modals
TODO: retry matching
1 parent 354f47f commit 79b3170

File tree

12 files changed

+145
-62
lines changed

12 files changed

+145
-62
lines changed

apps/frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"next": "14.2.13",
1717
"react": "^18.2.0",
1818
"react-dom": "^18.2.0",
19+
"react-timer-hook": "^3.0.7",
1920
"react-use-websocket": "^4.9.0",
2021
"sass": "^1.79.2",
2122
"typeface-montserrat": "^1.1.13"

apps/frontend/pnpm-lock.yaml

Lines changed: 12 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/matching/MatchingModal.tsx

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,66 +6,86 @@ 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 isClosable = ["timeout", "closed"].includes(matchingState.state);
2224

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-
};
25+
function close() {
26+
// clean up matching and closedType State
27+
if (matchingState.state === "timeout") {
28+
matchingState.ok();
29+
}
30+
_close();
31+
}
3832

3933
const renderModalContent = () => {
40-
switch (matchingState) {
41-
case 'finding':
42-
return <FindMatchContent/>;
34+
switch (matchingState.state) {
35+
case 'closed':
36+
switch (closedType) {
37+
case "finding":
38+
return <FindMatchContent beginMatch={matchingState.start}/>;
39+
case "cancelled":
40+
return <MatchCancelledContent
41+
reselect={() => {
42+
setClosedType("finding");
43+
}}
44+
retry={() => {}}
45+
/>;
46+
case "joined":
47+
return <JoinedMatchContent cancel={() => {
48+
setClosedType("cancelled");
49+
}}/>;
50+
}
4351
case 'matching':
44-
return <MatchingInProgressContent />;
52+
return <MatchingInProgressContent cancelMatch={() => {
53+
matchingState.cancel();
54+
setClosedType("cancelled");
55+
}}/>;
56+
case 'cancelling':
57+
return <MatchingInProgressContent cancelMatch={() => {}}/>;
58+
case 'starting':
59+
return <FindMatchContent beginMatch={() => {}}/>
4560
case 'found':
46-
return <MatchFound />;
47-
case 'joined':
48-
return <JoinedMatchContent />;
49-
case 'notFound':
50-
return <MatchNotFoundContent />;
51-
case 'cancelled':
52-
return <MatchCancelledContent />;
61+
return <MatchFoundContent
62+
cancel={() => {
63+
matchingState.ok();
64+
setClosedType("cancelled");
65+
}}
66+
join={() => {
67+
matchingState.ok();
68+
setClosedType("joined");
69+
}}
70+
/>
71+
case 'timeout':
72+
return <MatchNotFoundContent reselect={matchingState.ok} retry={() => {}}/>;
5373
default:
5474
throw new Error('Invalid matching state.');
5575
}
5676
};
5777

5878
return (
5979
<Modal open={isOpen}
60-
onCancel={onClose}
80+
onCancel={close}
6181
footer={null}
6282
closable={false}
6383
maskClosable={false}
6484
className="modal"
6585
>
6686
{renderModalContent()}
67-
{isClosableMatchingState() && (
68-
<button className="close-button" onClick={onClose}>Close</button>
87+
{isClosable && (
88+
<button className="close-button" onClick={close}>Close</button>
6989
)}
7090
</Modal>
7191
)

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

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type { SelectProps } from 'antd';
1212
import 'typeface-montserrat';
1313
import './styles.scss';
1414
import { handleFindMatch } from '../handlers';
15+
import useMatching, { type MatchRequestParams } from '@/app/services/use-matching';
1516

1617
interface DifficultySelectorProps {
1718
className?: string;
@@ -25,7 +26,11 @@ 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[]>([]);
3136

@@ -55,7 +60,13 @@ const FindMatchContent: React.FC = () => {
5560
/>
5661
</div>
5762
<button className="find-match-button"
58-
onClick={handleFindMatch}
63+
onClick={() => {
64+
beginMatch({
65+
type: "match_request",
66+
difficulties: selectedDifficulties,
67+
topics: selectedTopics,
68+
})
69+
}}
5970
>
6071
Find Match
6172
</button>
@@ -77,8 +88,8 @@ const DifficultySelector: React.FC<DifficultySelectorProps> = ({ selectedDifficu
7788
<Tag.CheckableTag
7889
className={`difficulty-tag ${difficultyOption.value}-tag`}
7990
key={difficultyOption.value}
80-
checked={selectedDifficulties.includes(difficultyOption.value)}
81-
onChange={() => handleChange(difficultyOption.value)}
91+
checked={selectedDifficulties.includes(difficultyOption.label)}
92+
onChange={() => handleChange(difficultyOption.label)}
8293
>
8394
{difficultyOption.label}
8495
</Tag.CheckableTag>
@@ -90,11 +101,8 @@ const DifficultySelector: React.FC<DifficultySelectorProps> = ({ selectedDifficu
90101
const TopicSelector: React.FC<TopicSelectorProps> = ({ selectedTopics, onChange}) => {
91102
const topicOptions: SelectProps[] = CategoriesOption;
92103

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

100108
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: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ 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+
}
11+
12+
const MatchCancelledContent: React.FC<Props> = ({retry, reselect}) => {
813
return (
914
<div className="match-cancelled-content">
1015
<div className="cancel-icon-container">
@@ -23,12 +28,12 @@ const MatchCancelledContent: React.FC = () => {
2328
Your match request has been cancelled after waiting {formatTime(83)}
2429
</div>
2530
<button className="retry-match-button"
26-
onClick={() => handleRetryMatch()}
31+
onClick={retry}
2732
>
2833
Retry
2934
</button>
3035
<button className="reselect-match-options-button"
31-
onClick={() => handleReselectMatchOptions()}
36+
onClick={reselect}
3237
>
3338
Reselect Options
3439
</button>

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,20 @@ import 'typeface-montserrat';
55
import './styles.scss';
66
import { handleCancelMatch, handleJoinMatch } from '../handlers';
77
import { formatTime } from '@/utils/DateTime';
8+
import { useTimer } from "react-timer-hook"
89

9-
const MatchFoundContent: React.FC = () => {
10+
interface Props {
11+
join(): void,
12+
cancel(): void,
13+
}
14+
15+
const TIMEOUT = 10;
16+
17+
const MatchFoundContent: React.FC<Props> = ({join, cancel}) => {
18+
const { totalSeconds } = useTimer({
19+
expiryTimestamp: new Date(TIMEOUT * 1000),
20+
});
21+
1022
return (
1123
<div className="matching-found-content">
1224
<div className="matched-profiles">
@@ -24,15 +36,15 @@ const MatchFoundContent: React.FC = () => {
2436
</div>
2537
<div className="match-status-label">Match Found!</div>
2638
<div className="match-status-message">
27-
Joining in... {formatTime(83)}
39+
Joining in... {formatTime(totalSeconds)}
2840
</div>
2941
<button className="join-match-button"
30-
onClick={() => handleJoinMatch()}
42+
onClick={join}
3143
>
3244
Join
3345
</button>
3446
<button className="cancel-match-button"
35-
onClick={() => handleCancelMatch()}
47+
onClick={cancel}
3648
>
3749
Cancel
3850
</button>

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

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

7-
const MatchNotFoundContent: React.FC = () => {
7+
const MatchNotFoundContent: React.FC<{
8+
retry(): void
9+
reselect(): void
10+
}> = ({
11+
retry, reselect
12+
}) => {
813
return (
914
<div className="joined-match-content">
1015
<div className="dissatisfied-icon-container">
@@ -23,12 +28,12 @@ const MatchNotFoundContent: React.FC = () => {
2328
Sorry, we could not find a match after {formatTime(83)}
2429
</div>
2530
<button className="retry-match-button"
26-
onClick={() => handleRetryMatch()}
31+
onClick={retry}
2732
>
2833
Retry
2934
</button>
3035
<button className="reselect-match-options-button"
31-
onClick={() => handleReselectMatchOptions()}
36+
onClick={reselect}
3237
>
3338
Reselect Options
3439
</button>

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,35 @@
1-
import React from 'react';
1+
import React, { useState } from 'react';
22
import { LoadingOutlined } from '@ant-design/icons';
33
import 'typeface-montserrat';
44
import './styles.scss';
55
import { handleCancelMatch } from '../handlers';
6+
import { useTimer } from "react-timer-hook"
67
import {formatTime} from '@/utils/DateTime';
78

8-
const MatchingInProgressContent: React.FC = () => {
9+
const TIMEOUT = 10;
10+
11+
interface Props {
12+
cancelMatch(): void
13+
}
14+
15+
const MatchingInProgressContent: React.FC<Props> = ({cancelMatch: cancel}) => {
16+
const time = new Date();
17+
time.setSeconds(time.getSeconds() + TIMEOUT);
18+
const { totalSeconds } = useTimer({
19+
expiryTimestamp: time,
20+
onExpire: cancel,
21+
});
22+
923
return (
1024
<div className="matching-in-progess-content">
1125
<LoadingOutlined className="loading-icon"/>
1226
<div className="match-status-label">Matching in Progress</div>
1327
<div className="match-status-message">
1428
Please be patient as we match you with other online users<br/><br/>
15-
{formatTime(83)}
29+
{formatTime(TIMEOUT - totalSeconds)}
1630
</div>
1731
<button className="cancel-match-button"
18-
onClick={() => handleCancelMatch()}
32+
onClick={cancel}
1933
>
2034
Cancel
2135
</button>

apps/frontend/src/app/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const HomePage = () => {
2020
<Layout className="layout">
2121
<Header selectedKey={undefined} />
2222
<Content className="content">
23-
<MatchingModal isOpen={matchingModalOpen} onClose={closeMatchingModal}/>
23+
<MatchingModal isOpen={matchingModalOpen} close={closeMatchingModal}/>
2424
<div className="home-content-card">
2525
<div className="left-panel">
2626
<div className="logo-container">

0 commit comments

Comments
 (0)