Skip to content

Commit

Permalink
Merge pull request #40 from CS3219-AY2425S1/frontend-websocket
Browse files Browse the repository at this point in the history
Frontend Websocket
  • Loading branch information
solomonng2001 authored Oct 20, 2024
2 parents f8e0cda + c0e2088 commit 0b5e5de
Show file tree
Hide file tree
Showing 20 changed files with 412 additions and 78 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches:
- main
- staging
- frontend-websocket-test
pull_request:
branches:
- main
Expand All @@ -27,6 +28,7 @@ jobs:
env:
QUESTION_SERVICE_URL: ${{ vars.QUESTION_SERVICE_URL }}
USER_SERVICE_URL: ${{ vars.USER_SERVICE_URL }}
MATCHING_SERVICE_URL: ${{ vars.MATCHING_SERVICE_URL }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
FIREBASE_CREDENTIAL_PATH: ${{ vars.QUESTION_SERVICE_FIREBASE_CREDENTIAL_PATH }}
DB_CLOUD_URI: ${{ secrets.USER_SERVICE_DB_CLOUD_URI }}
Expand All @@ -35,6 +37,7 @@ jobs:
cd ./apps/frontend
echo "NEXT_PUBLIC_QUESTION_SERVICE_URL=$QUESTION_SERVICE_URL" >> .env
echo "NEXT_PUBLIC_USER_SERVICE_URL=$USER_SERVICE_URL" >> .env
echo "NEXT_PUBLIC_MATCHING_SERVICE_URL=$MATCHING_SERVICE_URL" >> .env
cd ../question-service
echo "FIREBASE_CREDENTIAL_PATH=$FIREBASE_CREDENTIAL_PATH" >> .env
Expand Down
3 changes: 2 additions & 1 deletion apps/frontend/.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# URL endpoints of the services
NEXT_PUBLIC_QUESTION_SERVICE_URL="http://localhost:8080/"
NEXT_PUBLIC_USER_SERVICE_URL="http://localhost:3001/"
NEXT_PUBLIC_USER_SERVICE_URL="http://localhost:3001/"
NEXT_PUBLIC_MATCHING_SERVICE_URL="ws://localhost:8081/match"
1 change: 1 addition & 0 deletions apps/frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Then, follow the `.env.example` file and create a `.env` file in the current dir
```bash
NEXT_PUBLIC_QUESTION_SERVICE_URL="http://localhost:8080"
NEXT_PUBLIC_USER_SERVICE_URL="http://localhost:3001/"
NEXT_PUBLIC_MATCHING_SERVICE_URL="wss://localhost:8081"
```

First, run the development server:
Expand Down
2 changes: 2 additions & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"next": "14.2.13",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-timer-hook": "^3.0.7",
"react-use-websocket": "^4.9.0",
"sass": "^1.79.2",
"typeface-montserrat": "^1.1.13"
},
Expand Down
20 changes: 20 additions & 0 deletions apps/frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion apps/frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";
import { AntdRegistry } from "@ant-design/nextjs-registry";
import { ConfigProvider } from "antd";
import WebSocketProvider from "@/components/WebSocketProvider/websocketprovider";

const RootLayout = ({
children,
Expand All @@ -23,7 +24,11 @@ const RootLayout = ({
},
}}
>
<AntdRegistry>{children}</AntdRegistry>
<AntdRegistry>
<WebSocketProvider>
{children}
</WebSocketProvider>
</AntdRegistry>
</ConfigProvider>
</body>
</html>
Expand Down
100 changes: 66 additions & 34 deletions apps/frontend/src/app/matching/MatchingModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,66 +6,98 @@ import 'typeface-montserrat';
import './styles.scss';
import FindMatchContent from './modalContent/FindMatchContent';
import MatchingInProgressContent from './modalContent/MatchingInProgressContent';
import MatchFound from './modalContent/MatchFoundContent';
import MatchFoundContent from './modalContent/MatchFoundContent';
import JoinedMatchContent from './modalContent/JoinedMatchContent';
import MatchNotFoundContent from './modalContent/MatchNotFoundContent';
import MatchCancelledContent from './modalContent/MatchCancelledContent';
import useMatching from '../services/use-matching';

interface MatchingModalProps {
isOpen: boolean;
onClose: () => void;
close: () => void;
}

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

// TODO: remove this after testing
useEffect(() => {
// Uncomment the following lines to test the different matching states
// setMatchingState('finding');
// setMatchingState('matching');
// setMatchingState('found');
// setMatchingState('joined');
// setMatchingState('notFound');
// setMatchingState('cancelled');
}, []);

// TODO: modify by using matchingState via useContext
const isClosableMatchingState = () => {
return matchingState === 'finding' || matchingState === 'notFound' || matchingState === 'cancelled';
};
function close() {
// clean up matching and closedType State
if (matchingState.state === "timeout") {
matchingState.ok();
}
setClosedType("finding");
_close();
}

const renderModalContent = () => {
switch (matchingState) {
case 'finding':
return <FindMatchContent/>;
switch (matchingState.state) {
case 'closed':
switch (closedType) {
case "finding":
return <FindMatchContent beginMatch={matchingState.start}/>;
case "cancelled":
return <MatchCancelledContent
reselect={() => {
setClosedType("finding");
}}
retry={() => {}}
canceledIn={timeoutAfter}
/>;
case "joined":
return <JoinedMatchContent cancel={() => {
setClosedType("cancelled");
}}/>;
}
case 'matching':
return <MatchingInProgressContent />;
return <MatchingInProgressContent
cancelMatch={(timeoutAfter: number) => {
setClosedType("cancelled");
setTimeoutAfter(timeoutAfter);
matchingState.cancel();
}}
timeout={(timeoutAfter: number) => {
matchingState.timeout()
setTimeoutAfter(timeoutAfter);
}}
/>;
case 'cancelling':
return <MatchingInProgressContent cancelMatch={() => {}} timeout={() => {}}/>;
case 'starting':
return <FindMatchContent beginMatch={() => {}}/>
case 'found':
return <MatchFound />;
case 'joined':
return <JoinedMatchContent />;
case 'notFound':
return <MatchNotFoundContent />;
case 'cancelled':
return <MatchCancelledContent />;
return <MatchFoundContent
cancel={() => {
matchingState.ok();
setClosedType("cancelled");
}}
join={() => {
matchingState.ok();
setClosedType("joined");
}}
name1={matchingState.info.myName}
name2={matchingState.info.partnerName}
/>
case 'timeout':
return <MatchNotFoundContent reselect={matchingState.ok} retry={() => {}} timedOutIn={10}/>;
default:
throw new Error('Invalid matching state.');
}
};

return (
<Modal open={isOpen}
onCancel={onClose}
onCancel={close}
footer={null}
closable={false}
maskClosable={false}
className="modal"
>
{renderModalContent()}
{isClosableMatchingState() && (
<button className="close-button" onClick={onClose}>Close</button>
{isClosable && (
<button className="close-button" onClick={close}>Close</button>
)}
</Modal>
)
Expand Down
34 changes: 24 additions & 10 deletions apps/frontend/src/app/matching/modalContent/FindMatchContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
import type { SelectProps } from 'antd';
import 'typeface-montserrat';
import './styles.scss';
import { handleFindMatch } from '../handlers';
import { ValidateUser } from "@/app/services/user"
import { type MatchRequestParams } from '@/app/services/use-matching';

interface DifficultySelectorProps {
className?: string;
Expand All @@ -25,9 +26,14 @@ interface TopicSelectorProps {
onChange: (topics: string[]) => void;
}

const FindMatchContent: React.FC = () => {
interface Props {
beginMatch(request: MatchRequestParams): void
}

const FindMatchContent: React.FC<Props> = ({ beginMatch }) => {
const [selectedDifficulties, setSelectedDifficulties] = useState<string[]>([]);
const [selectedTopics, setSelectedTopics] = useState<string[]>([]);
const [isLoading, setIsLoading] = useState(false);

const handleDifficultyChange = (difficulties: string[]) => {
setSelectedDifficulties(difficulties);
Expand Down Expand Up @@ -55,7 +61,18 @@ const FindMatchContent: React.FC = () => {
/>
</div>
<button className="find-match-button"
onClick={handleFindMatch}
onClick={async () => {
setIsLoading(true);
const user = await ValidateUser();
beginMatch({
email: user.data.email,
username: user.data.username,
type: "match_request",
difficulties: selectedDifficulties,
topics: selectedTopics,
})
}}
disabled={isLoading}
>
Find Match
</button>
Expand All @@ -77,8 +94,8 @@ const DifficultySelector: React.FC<DifficultySelectorProps> = ({ selectedDifficu
<Tag.CheckableTag
className={`difficulty-tag ${difficultyOption.value}-tag`}
key={difficultyOption.value}
checked={selectedDifficulties.includes(difficultyOption.value)}
onChange={() => handleChange(difficultyOption.value)}
checked={selectedDifficulties.includes(difficultyOption.label)}
onChange={() => handleChange(difficultyOption.label)}
>
{difficultyOption.label}
</Tag.CheckableTag>
Expand All @@ -90,11 +107,8 @@ const DifficultySelector: React.FC<DifficultySelectorProps> = ({ selectedDifficu
const TopicSelector: React.FC<TopicSelectorProps> = ({ selectedTopics, onChange}) => {
const topicOptions: SelectProps[] = CategoriesOption;

const handleChange = (topic: string) => {
const newSelectedTopics = selectedTopics.includes(topic)
? selectedTopics.filter(selectedTopic => selectedTopic !== topic)
: [...selectedTopics, topic];
onChange(newSelectedTopics);
const handleChange = (topics: string[]) => {
onChange(topics);
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import './styles.scss';
import { handleCancelMatch } from '../handlers';
import { formatTime } from '@/utils/DateTime';

const JoinedMatchContent: React.FC = () => {

interface Props {
cancel(): void
}

const JoinedMatchContent: React.FC<Props> = ({cancel}) => {
const matchAlreadyJoined = () => {
throw new Error('Match already joined.');
}
Expand All @@ -36,12 +41,11 @@ const JoinedMatchContent: React.FC = () => {
</div>
<button className="joined-match-deactivated-button"
disabled
onClick={() => matchAlreadyJoined()}
>
Joined
</button>
<button className="cancel-match-button"
onClick={() => handleCancelMatch()}
onClick={cancel}
>
Cancel
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import './styles.scss';
import { handleReselectMatchOptions, handleRetryMatch } from '../handlers';
import { formatTime } from '@/utils/DateTime';

const MatchCancelledContent: React.FC = () => {
interface Props {
retry(): void,
reselect(): void,
canceledIn: number,
}

const MatchCancelledContent: React.FC<Props> = ({retry, reselect, canceledIn}) => {
return (
<div className="match-cancelled-content">
<div className="cancel-icon-container">
Expand All @@ -20,15 +26,15 @@ const MatchCancelledContent: React.FC = () => {
</div>
<div className="match-status-label">Match Cancelled!</div>
<div className="match-status-message">
Your match request has been cancelled after waiting {formatTime(83)}
Your match request has been cancelled after waiting {formatTime(canceledIn)}
</div>
<button className="retry-match-button"
onClick={() => handleRetryMatch()}
{/* <button className="retry-match-button"
onClick={retry}
>
Retry
</button>
</button> */}
<button className="reselect-match-options-button"
onClick={() => handleReselectMatchOptions()}
onClick={reselect}
>
Reselect Options
</button>
Expand Down
Loading

0 comments on commit 0b5e5de

Please sign in to comment.