Skip to content

Commit 354f47f

Browse files
committed
Merge branch 'staging' into frontend-websocket
2 parents 82af041 + dae6adb commit 354f47f

16 files changed

+721
-3
lines changed

apps/frontend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"react": "^18.2.0",
1818
"react-dom": "^18.2.0",
1919
"react-use-websocket": "^4.9.0",
20-
"sass": "^1.79.2"
20+
"sass": "^1.79.2",
21+
"typeface-montserrat": "^1.1.13"
2122
},
2223
"devDependencies": {
2324
"@types/node": "^20",

apps/frontend/pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import React, { useState, useEffect } from 'react';
2+
import {
3+
Modal,
4+
} from 'antd';
5+
import 'typeface-montserrat';
6+
import './styles.scss';
7+
import FindMatchContent from './modalContent/FindMatchContent';
8+
import MatchingInProgressContent from './modalContent/MatchingInProgressContent';
9+
import MatchFound from './modalContent/MatchFoundContent';
10+
import JoinedMatchContent from './modalContent/JoinedMatchContent';
11+
import MatchNotFoundContent from './modalContent/MatchNotFoundContent';
12+
import MatchCancelledContent from './modalContent/MatchCancelledContent';
13+
14+
interface MatchingModalProps {
15+
isOpen: boolean;
16+
onClose: () => void;
17+
}
18+
19+
const MatchingModal: React.FC<MatchingModalProps> = ({ isOpen, onClose }) => {
20+
// TODO: placehoder for now, to be replaced my useContext
21+
const [matchingState, setMatchingState] = useState('finding');
22+
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+
};
38+
39+
const renderModalContent = () => {
40+
switch (matchingState) {
41+
case 'finding':
42+
return <FindMatchContent/>;
43+
case 'matching':
44+
return <MatchingInProgressContent />;
45+
case 'found':
46+
return <MatchFound />;
47+
case 'joined':
48+
return <JoinedMatchContent />;
49+
case 'notFound':
50+
return <MatchNotFoundContent />;
51+
case 'cancelled':
52+
return <MatchCancelledContent />;
53+
default:
54+
throw new Error('Invalid matching state.');
55+
}
56+
};
57+
58+
return (
59+
<Modal open={isOpen}
60+
onCancel={onClose}
61+
footer={null}
62+
closable={false}
63+
maskClosable={false}
64+
className="modal"
65+
>
66+
{renderModalContent()}
67+
{isClosableMatchingState() && (
68+
<button className="close-button" onClick={onClose}>Close</button>
69+
)}
70+
</Modal>
71+
)
72+
}
73+
74+
export default MatchingModal;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export const handleFindMatch = () => {
2+
// start timer
3+
// open socket connection
4+
// change matching state to 'matching'
5+
}
6+
7+
export const handleCancelMatch = () => {
8+
// close socket connection
9+
// stop timer
10+
// change matching state to 'cancelled'
11+
}
12+
13+
// TODO: wait for collaboration service
14+
export const handleJoinMatch = () => {
15+
// notify server that user has joined the match (close connection?)
16+
// change matching state to 'joined'
17+
}
18+
19+
export const handleRetryMatch = () => {
20+
// retain current difficulty and topics
21+
// same as handleFindMatch
22+
}
23+
24+
export const handleReselectMatchOptions = () => {
25+
// reset selected difficulties and topics
26+
// same as handleFindMatch
27+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import React, { useState } from 'react';
2+
import {
3+
Tag,
4+
Select,
5+
Space,
6+
} from 'antd';
7+
import {
8+
DifficultyOption,
9+
CategoriesOption,
10+
} from '@/utils/SelectOptions';
11+
import type { SelectProps } from 'antd';
12+
import 'typeface-montserrat';
13+
import './styles.scss';
14+
import { handleFindMatch } from '../handlers';
15+
16+
interface DifficultySelectorProps {
17+
className?: string;
18+
selectedDifficulties: string[];
19+
onChange: (difficulties: string[]) => void;
20+
}
21+
22+
interface TopicSelectorProps {
23+
className?: string;
24+
selectedTopics: string[];
25+
onChange: (topics: string[]) => void;
26+
}
27+
28+
const FindMatchContent: React.FC = () => {
29+
const [selectedDifficulties, setSelectedDifficulties] = useState<string[]>([]);
30+
const [selectedTopics, setSelectedTopics] = useState<string[]>([]);
31+
32+
const handleDifficultyChange = (difficulties: string[]) => {
33+
setSelectedDifficulties(difficulties);
34+
};
35+
36+
const handleTopicChange = (topics: string[]) => {
37+
setSelectedTopics(topics);
38+
};
39+
40+
return (
41+
<div>
42+
<div className="find-match-title">Find Match</div>
43+
<div className="difficulty-label">Difficulty</div>
44+
<div className="difficulty-selector">
45+
<DifficultySelector
46+
selectedDifficulties={selectedDifficulties}
47+
onChange={handleDifficultyChange}
48+
/>
49+
</div>
50+
<div className="topic-label">Topic</div>
51+
<div className="topic-selector">
52+
<TopicSelector
53+
selectedTopics={selectedTopics}
54+
onChange={handleTopicChange}
55+
/>
56+
</div>
57+
<button className="find-match-button"
58+
onClick={handleFindMatch}
59+
>
60+
Find Match
61+
</button>
62+
</div>
63+
)
64+
}
65+
66+
const DifficultySelector: React.FC<DifficultySelectorProps> = ({ selectedDifficulties, onChange}) => {
67+
const handleChange = (difficulty: string) => {
68+
const newSelectedDifficulties = selectedDifficulties.includes(difficulty)
69+
? selectedDifficulties.filter(selectedDifficulty => selectedDifficulty !== difficulty)
70+
: [...selectedDifficulties, difficulty];
71+
onChange(newSelectedDifficulties);
72+
}
73+
74+
return (
75+
<div>
76+
{DifficultyOption.map(difficultyOption => (
77+
<Tag.CheckableTag
78+
className={`difficulty-tag ${difficultyOption.value}-tag`}
79+
key={difficultyOption.value}
80+
checked={selectedDifficulties.includes(difficultyOption.value)}
81+
onChange={() => handleChange(difficultyOption.value)}
82+
>
83+
{difficultyOption.label}
84+
</Tag.CheckableTag>
85+
))}
86+
</div>
87+
)
88+
}
89+
90+
const TopicSelector: React.FC<TopicSelectorProps> = ({ selectedTopics, onChange}) => {
91+
const topicOptions: SelectProps[] = CategoriesOption;
92+
93+
const handleChange = (topic: string) => {
94+
const newSelectedTopics = selectedTopics.includes(topic)
95+
? selectedTopics.filter(selectedTopic => selectedTopic !== topic)
96+
: [...selectedTopics, topic];
97+
onChange(newSelectedTopics);
98+
}
99+
100+
return (
101+
<div className="find-match-content">
102+
<Space className="select-space" direction="vertical">
103+
<Select
104+
className="select-topic"
105+
mode="multiple"
106+
allowClear
107+
style={{ width: '100%' }}
108+
placeholder="Select Topic(s)"
109+
onChange={handleChange}
110+
options={topicOptions}
111+
/>
112+
</Space>
113+
</div>
114+
)
115+
}
116+
117+
export default FindMatchContent;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React from 'react';
2+
import {
3+
Avatar,
4+
} from 'antd';
5+
import {
6+
UserOutlined,
7+
} from '@ant-design/icons';
8+
import 'typeface-montserrat';
9+
import './styles.scss';
10+
import { handleCancelMatch } from '../handlers';
11+
import { formatTime } from '@/utils/DateTime';
12+
13+
const JoinedMatchContent: React.FC = () => {
14+
const matchAlreadyJoined = () => {
15+
throw new Error('Match already joined.');
16+
}
17+
18+
return (
19+
<div className="joined-match-content">
20+
<div className="matched-profiles">
21+
<Avatar size={64} icon={<UserOutlined />} />
22+
<svg xmlns="http://www.w3.org/2000/svg"
23+
height="24px"
24+
viewBox="0 -960 960 960"
25+
width="24px"
26+
fill="#e8eaed"
27+
className="bolt-icon"
28+
>
29+
<path d="m422-232 207-248H469l29-227-185 267h139l-30 208ZM320-80l40-280H160l360-520h80l-40 320h240L400-80h-80Zm151-390Z"/>
30+
</svg>
31+
<Avatar size={64} icon={<UserOutlined />} />
32+
</div>
33+
<div className="match-status-label">Match Found!</div>
34+
<div className="match-status-message">
35+
Waiting for others... {formatTime(83)}
36+
</div>
37+
<button className="joined-match-deactivated-button"
38+
disabled
39+
onClick={() => matchAlreadyJoined()}
40+
>
41+
Joined
42+
</button>
43+
<button className="cancel-match-button"
44+
onClick={() => handleCancelMatch()}
45+
>
46+
Cancel
47+
</button>
48+
</div>
49+
)
50+
}
51+
52+
export default JoinedMatchContent;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React from 'react';
2+
import 'typeface-montserrat';
3+
import './styles.scss';
4+
import { handleReselectMatchOptions, handleRetryMatch } from '../handlers';
5+
import { formatTime } from '@/utils/DateTime';
6+
7+
const MatchCancelledContent: React.FC = () => {
8+
return (
9+
<div className="match-cancelled-content">
10+
<div className="cancel-icon-container">
11+
<svg xmlns="http://www.w3.org/2000/svg"
12+
height="24px"
13+
viewBox="0 -960 960 960"
14+
width="24px"
15+
fill="#e8eaed"
16+
className="cancel-icon"
17+
>
18+
<path d="m336-280 144-144 144 144 56-56-144-144 144-144-56-56-144 144-144-144-56 56 144 144-144 144 56 56ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/>
19+
</svg>
20+
</div>
21+
<div className="match-status-label">Match Cancelled!</div>
22+
<div className="match-status-message">
23+
Your match request has been cancelled after waiting {formatTime(83)}
24+
</div>
25+
<button className="retry-match-button"
26+
onClick={() => handleRetryMatch()}
27+
>
28+
Retry
29+
</button>
30+
<button className="reselect-match-options-button"
31+
onClick={() => handleReselectMatchOptions()}
32+
>
33+
Reselect Options
34+
</button>
35+
</div>
36+
)
37+
}
38+
39+
export default MatchCancelledContent;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from 'react';
2+
import { Avatar } from 'antd';
3+
import { UserOutlined } from '@ant-design/icons';
4+
import 'typeface-montserrat';
5+
import './styles.scss';
6+
import { handleCancelMatch, handleJoinMatch } from '../handlers';
7+
import { formatTime } from '@/utils/DateTime';
8+
9+
const MatchFoundContent: React.FC = () => {
10+
return (
11+
<div className="matching-found-content">
12+
<div className="matched-profiles">
13+
<Avatar size={64} icon={<UserOutlined />} />
14+
<svg xmlns="http://www.w3.org/2000/svg"
15+
height="24px"
16+
viewBox="0 -960 960 960"
17+
width="24px"
18+
fill="#e8eaed"
19+
className="bolt-icon"
20+
>
21+
<path d="m422-232 207-248H469l29-227-185 267h139l-30 208ZM320-80l40-280H160l360-520h80l-40 320h240L400-80h-80Zm151-390Z"/>
22+
</svg>
23+
<Avatar size={64} icon={<UserOutlined />} />
24+
</div>
25+
<div className="match-status-label">Match Found!</div>
26+
<div className="match-status-message">
27+
Joining in... {formatTime(83)}
28+
</div>
29+
<button className="join-match-button"
30+
onClick={() => handleJoinMatch()}
31+
>
32+
Join
33+
</button>
34+
<button className="cancel-match-button"
35+
onClick={() => handleCancelMatch()}
36+
>
37+
Cancel
38+
</button>
39+
</div>
40+
)
41+
}
42+
43+
export default MatchFoundContent;

0 commit comments

Comments
 (0)