Skip to content

Commit

Permalink
Merge pull request #76 from CS3219-AY2425S1/feat/fe-for-matching
Browse files Browse the repository at this point in the history
Feat/fe for matching
  • Loading branch information
Kurtyjlee authored Oct 20, 2024
2 parents 9a4a8ec + da83053 commit 3d30a01
Show file tree
Hide file tree
Showing 9 changed files with 406 additions and 261 deletions.
1 change: 1 addition & 0 deletions peerprep-fe/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"zustand": "5.0.0-rc.2"
},
"devDependencies": {
"@types/amqplib": "^0.10.5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand Down
16 changes: 13 additions & 3 deletions peerprep-fe/pnpm-lock.yaml

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

10 changes: 8 additions & 2 deletions peerprep-fe/src/app/(main)/components/filter/FilterSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { FilterSelectProps } from '@/types/types';

Expand All @@ -12,15 +13,20 @@ export function FilterSelect({
onChange,
isMulti,
value,
}: FilterSelectProps) {
showSelectedValue = false, // New prop to control the display behavior
}: FilterSelectProps & { showSelectedValue?: boolean }) {
return (
<Select
onValueChange={onChange}
{...(isMulti ? { multiple: true } : {})}
value={isMulti ? undefined : (value as string)}
>
<SelectTrigger className="w-[120px] border-gray-700 bg-gray-800">
{placeholder}
{showSelectedValue ? (
<SelectValue placeholder={placeholder} />
) : (
placeholder
)}
</SelectTrigger>
<SelectContent>
{options.map((option) => (
Expand Down
151 changes: 116 additions & 35 deletions peerprep-fe/src/app/(main)/match/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,38 @@ import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { User, Code } from 'lucide-react';
import { consumeMessageFromQueue } from '@/lib/rabbitmq';
import { Button } from '@/components/ui/button';
import { useAuthStore } from '@/state/useAuthStore';

export default function LoadingPage() {
const [elapsedTime, setElapsedTime] = useState(0);
const [usersWaiting, setUsersWaiting] = useState(4);
const [matchStatus, setMatchStatus] = useState('searching');
const router = useRouter();
const { user } = useAuthStore();

const startConsumingMessages = async () => {
try {
await consumeMessageFromQueue().then((message) => {
// This function is called when a message is consumed
if (message.status == 'matched') {
console.log('Match found, your partner is');
router.push('/');
await consumeMessageFromQueue(user?.id!).then((message) => {
if (message.status === 'matched') {
console.log('Match found, your partner is', message.partner);
setMatchStatus('matched');

// setTimeout(() => {
// router.push(`/collaboration`);
// }, 2000);
} else {
console.log('Match failed');
router.push('/');
setMatchStatus('failed');

// setTimeout(() => {
// router.push(`/`);
// }, 4500);
}
});
} catch (error) {
console.error('Error consuming message:', error);
setMatchStatus('error');
}
};

Expand All @@ -37,12 +49,17 @@ export default function LoadingPage() {
}, []);

useEffect(() => {
if (elapsedTime >= 60) {
// Execute your action here
console.log('Elapsed time reached 60 seconds. Going back to main page');
router.push('/');
if (elapsedTime >= 60 && matchStatus === 'searching') {
console.log('Elapsed time reached 60 seconds. Match timed out.');
setMatchStatus('timeout');
}
}, [elapsedTime]);
}, [elapsedTime, matchStatus]);

const handleCancel = () => {
// Implement logic to cancel the matching process
console.log('Matching cancelled');
router.push('/');
};

return (
<div className="flex min-h-screen flex-col bg-[#1a1f2e] text-gray-300">
Expand All @@ -54,30 +71,94 @@ export default function LoadingPage() {
<User className="h-6 w-6" />
</header>
<main className="flex flex-grow flex-col items-center justify-center space-y-6 px-4">
<div className="h-16 w-16 animate-spin rounded-full border-b-4 border-t-4 border-purple-500"></div>
<h1 className="text-2xl font-bold text-white">Finding a match</h1>
<p className="max-w-md text-center text-sm">
We&apos;re pairing you with another coder. This may take a few
moments.
</p>
<div className="w-full max-w-md space-y-2">
<div className="h-1 overflow-hidden rounded-full bg-gray-700">
<div
className="h-full rounded-full bg-purple-500"
style={{ width: `${((elapsedTime % 60) / 60) * 100}%` }}
></div>
</div>
<div className="text-center text-sm">
Time elapsed: {elapsedTime} seconds
</div>
</div>
<div className="flex items-center space-x-2 text-sm">
<User className="h-4 w-4" />
<span>{usersWaiting} users waiting</span>
</div>
<button className="w-full max-w-md rounded-md bg-purple-600 px-4 py-2 text-white transition-colors hover:bg-purple-700">
Cancel Matching
</button>
{matchStatus === 'searching' && (
<>
<div className="h-16 w-16 animate-spin rounded-full border-b-4 border-t-4 border-purple-500"></div>
<h1 className="text-2xl font-bold text-white">Finding a match</h1>
<p className="max-w-md text-center text-sm">
We&apos;re pairing you with another coder. This may take a few
moments.
</p>
<div className="w-full max-w-md space-y-2">
<div className="h-1 overflow-hidden rounded-full bg-gray-700">
<div
className="h-full rounded-full bg-purple-500"
style={{ width: `${((elapsedTime % 60) / 60) * 100}%` }}
></div>
</div>
<div className="text-center text-sm">
Time elapsed: {elapsedTime} seconds
</div>
</div>
<div className="flex items-center space-x-2 text-sm">
<User className="h-4 w-4" />
<span>{usersWaiting} users waiting</span>
</div>
<Button
onClick={handleCancel}
className="w-full max-w-md bg-purple-600 hover:bg-purple-700"
>
Cancel Matching
</Button>
</>
)}
{matchStatus === 'matched' && (
<>
<div className="h-16 w-16 animate-bounce text-4xl">🎉</div>
<h1 className="text-2xl font-bold text-white">Match Found!</h1>
<p className="max-w-md text-center text-sm">
Great news! We&apos;ve found a coding partner for you. Redirecting
to your collaboration room...
</p>
</>
)}
{matchStatus === 'failed' && (
<>
<div className="h-16 w-16 text-4xl">😕</div>
<h1 className="text-2xl font-bold text-white">Match Failed</h1>
<p className="max-w-md text-center text-sm">
We couldn&apos;t find a suitable match at this time. Please try
again later.
</p>
<Button
onClick={() => router.push('/')}
className="w-full max-w-md bg-purple-600 hover:bg-purple-700"
>
Return to Home
</Button>
</>
)}
{matchStatus === 'timeout' && (
<>
<div className="h-16 w-16 text-4xl"></div>
<h1 className="text-2xl font-bold text-white">Match Timed Out</h1>
<p className="max-w-md text-center text-sm">
We couldn&apos;t find a match within the time limit. Please try
again.
</p>
<Button
onClick={() => router.push('/')}
className="w-full max-w-md bg-purple-600 hover:bg-purple-700"
>
Return to Home
</Button>
</>
)}
{matchStatus === 'error' && (
<>
<div className="h-16 w-16 text-4xl"></div>
<h1 className="text-2xl font-bold text-white">Error Occurred</h1>
<p className="max-w-md text-center text-sm">
An error occurred while finding a match. Please try again later.
</p>
<Button
onClick={() => router.push('/')}
className="w-full max-w-md bg-purple-600 hover:bg-purple-700"
>
Return to Home
</Button>
</>
)}
<p className="mt-4 text-sm text-gray-500">
Tip: While you wait, why not review some coding concepts?
</p>
Expand Down
9 changes: 9 additions & 0 deletions peerprep-fe/src/app/collaboration/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';

type Props = {};

const page = (props: Props) => {
return <div>This is the collaboration page</div>;
};

export default page;
91 changes: 91 additions & 0 deletions peerprep-fe/src/components/dialogs/PreMatch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { FilterSelect } from '@/app/(main)/components/filter/FilterSelect';
import { TopicsPopover } from '@/app/(main)/components/filter/TopicsPopover';
import { sendMessageToQueue } from '@/lib/rabbitmq';
import { axiosAuthClient } from '@/network/axiosClient';
import { DIFFICULTY_OPTIONS } from '@/lib/constants';

export function PreMatch() {
const [open, setOpen] = useState(false);
const [difficulty, setDifficulty] = useState('');
const [selectedTopics, setSelectedTopics] = useState<string[]>([]);
const router = useRouter();

const handleConfirm = async () => {
try {
const profileDetails = await getProfileDetails();
const message = {
_id: profileDetails.id,
name: profileDetails.username,
topic: selectedTopics[0] || '', // TODO: change to list, but current backend only accepts 1
// topic: selectedTopics.join(','),
difficulty: difficulty,
};
await sendMessageToQueue(message);
setOpen(false);
router.push('/match');
} catch (err) {
console.error('Error in handleConfirm:', err);
}
};

const getProfileDetails = async () => {
const result = await axiosAuthClient.get('/auth/verify-token');
return result.data.data;
};

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="ghost" className="text-gray-300 hover:text-white">
Match
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Choose Match Preferences</DialogTitle>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="flex items-center gap-4">
<label htmlFor="difficulty" className="text-right">
Difficulty:
</label>
<FilterSelect
placeholder="Difficulty"
options={DIFFICULTY_OPTIONS}
onChange={(value) => setDifficulty(value)}
value={difficulty}
showSelectedValue={true}
/>
</div>
<div className="flex items-center gap-4">
<label htmlFor="topics" className="text-right">
Topics:
</label>
<TopicsPopover
selectedTopics={selectedTopics}
onChange={setSelectedTopics}
/>
</div>
</div>
<Button
onClick={handleConfirm}
disabled={!difficulty || selectedTopics.length === 0}
>
Confirm and Match
</Button>
</DialogContent>
</Dialog>
);
}
Loading

0 comments on commit 3d30a01

Please sign in to comment.