Skip to content

Commit d66ba75

Browse files
authored
Merge pull request #171 from CS3219-AY2425S1/feat/matching/frontend-ui
Frontend UI For matching
2 parents fc4f598 + e8cda63 commit d66ba75

File tree

10 files changed

+411
-234
lines changed

10 files changed

+411
-234
lines changed

frontend/app/app/matching/page.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import AuthPageWrapper from "@/components/auth/auth-page-wrapper";
2+
import FindMatch from "@/components/matching/find-match";
3+
import { Suspense } from "react";
4+
5+
export default function MatchingPage() {
6+
return (
7+
<AuthPageWrapper requireLoggedIn>
8+
<Suspense>
9+
<FindMatch />
10+
</Suspense>
11+
</AuthPageWrapper>
12+
);
13+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"use client";
2+
import React, { useState, useEffect } from "react";
3+
import { MatchForm } from "@/components/matching/matching-form";
4+
import { SearchProgress } from "@/components/matching/search-progress";
5+
import { SelectionSummary } from "@/components/matching/selection-summary";
6+
import { useToast } from "@/components/hooks/use-toast";
7+
8+
export default function FindMatch() {
9+
const [selectedDifficulty, setSelectedDifficulty] = useState<string>("");
10+
const [selectedTopic, setSelectedTopic] = useState<string>("");
11+
const [isSearching, setIsSearching] = useState<boolean>(false);
12+
const [waitTime, setWaitTime] = useState<number>(0);
13+
const { toast } = useToast();
14+
15+
useEffect(() => {
16+
let interval: NodeJS.Timeout | undefined;
17+
if (isSearching) {
18+
interval = setInterval(() => {
19+
setWaitTime((prevTime) => prevTime + 1);
20+
}, 1000);
21+
} else {
22+
setWaitTime(0);
23+
}
24+
return () => clearInterval(interval);
25+
}, [isSearching]);
26+
27+
const handleSearch = () => {
28+
if (selectedDifficulty && selectedTopic) {
29+
setIsSearching(true);
30+
} else {
31+
toast({
32+
title: "Invalid Selection",
33+
description: "Please select both a difficulty level and a topic",
34+
variant: "destructive",
35+
});
36+
}
37+
};
38+
39+
const handleCancel = () => {
40+
setIsSearching(false);
41+
setWaitTime(0);
42+
};
43+
44+
return (
45+
<div className="container mx-auto p-4">
46+
<MatchForm
47+
selectedDifficulty={selectedDifficulty}
48+
setSelectedDifficulty={setSelectedDifficulty}
49+
selectedTopic={selectedTopic}
50+
setSelectedTopic={setSelectedTopic}
51+
handleSearch={handleSearch}
52+
isSearching={isSearching}
53+
handleCancel={handleCancel}
54+
/>
55+
56+
{isSearching && <SearchProgress waitTime={waitTime} />}
57+
58+
{!isSearching && (selectedDifficulty || selectedTopic) && (
59+
<SelectionSummary
60+
selectedDifficulty={selectedDifficulty}
61+
selectedTopic={selectedTopic}
62+
/>
63+
)}
64+
</div>
65+
);
66+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import React from "react";
2+
import { Button } from "@/components/ui/button";
3+
import {
4+
Card,
5+
CardContent,
6+
CardDescription,
7+
CardFooter,
8+
CardHeader,
9+
CardTitle,
10+
} from "@/components/ui/card";
11+
import {
12+
Select,
13+
SelectContent,
14+
SelectItem,
15+
SelectTrigger,
16+
SelectValue,
17+
} from "@/components/ui/select";
18+
import { Label } from "@/components/ui/label";
19+
20+
const difficulties: string[] = ["Easy", "Medium", "Hard"];
21+
const topics: string[] = [
22+
"Arrays",
23+
"Strings",
24+
"Linked Lists",
25+
"Trees",
26+
"Graphs",
27+
"Dynamic Programming",
28+
];
29+
30+
interface MatchFormProps {
31+
selectedDifficulty: string;
32+
setSelectedDifficulty: (difficulty: string) => void;
33+
selectedTopic: string;
34+
setSelectedTopic: (topic: string) => void;
35+
handleSearch: () => void;
36+
isSearching: boolean;
37+
handleCancel: () => void;
38+
}
39+
40+
export function MatchForm({
41+
selectedDifficulty,
42+
setSelectedDifficulty,
43+
selectedTopic,
44+
setSelectedTopic,
45+
handleSearch,
46+
isSearching,
47+
handleCancel,
48+
}: MatchFormProps) {
49+
return (
50+
<Card className="w-full max-w-2xl mx-auto">
51+
<CardHeader>
52+
<CardTitle>Find a Match</CardTitle>
53+
<CardDescription>
54+
Select your preferred difficulty level and topic to find a match.
55+
</CardDescription>
56+
</CardHeader>
57+
<CardContent>
58+
<div className="space-y-6">
59+
<div>
60+
<Label
61+
htmlFor="difficulty-select"
62+
className="text-lg font-medium mb-2 block"
63+
>
64+
Difficulty Level
65+
</Label>
66+
<Select
67+
value={selectedDifficulty}
68+
onValueChange={setSelectedDifficulty}
69+
>
70+
<SelectTrigger id="difficulty-select">
71+
<SelectValue placeholder="Select difficulty" />
72+
</SelectTrigger>
73+
<SelectContent>
74+
{difficulties.map((difficulty) => (
75+
<SelectItem key={difficulty} value={difficulty}>
76+
{difficulty}
77+
</SelectItem>
78+
))}
79+
</SelectContent>
80+
</Select>
81+
</div>
82+
<div>
83+
<Label
84+
htmlFor="topic-select"
85+
className="text-lg font-medium mb-2 block"
86+
>
87+
Topic
88+
</Label>
89+
<Select value={selectedTopic} onValueChange={setSelectedTopic}>
90+
<SelectTrigger id="topic-select">
91+
<SelectValue placeholder="Select topic" />
92+
</SelectTrigger>
93+
<SelectContent>
94+
{topics.map((topic) => (
95+
<SelectItem key={topic} value={topic}>
96+
{topic}
97+
</SelectItem>
98+
))}
99+
</SelectContent>
100+
</Select>
101+
</div>
102+
</div>
103+
</CardContent>
104+
<CardFooter className="flex justify-between">
105+
{!isSearching ? (
106+
<Button onClick={handleSearch}>Find Match</Button>
107+
) : (
108+
<Button variant="destructive" onClick={handleCancel}>
109+
Cancel Search
110+
</Button>
111+
)}
112+
</CardFooter>
113+
</Card>
114+
);
115+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React from "react";
2+
import {
3+
Card,
4+
CardContent,
5+
CardDescription,
6+
CardHeader,
7+
CardTitle,
8+
} from "@/components/ui/card";
9+
import { Progress } from "@/components/ui/progress";
10+
import { Clock } from "lucide-react";
11+
12+
interface SearchProgressProps {
13+
waitTime: number;
14+
}
15+
16+
export function SearchProgress({ waitTime }: SearchProgressProps) {
17+
return (
18+
<Card className="w-full max-w-2xl mx-auto mt-4">
19+
<CardHeader>
20+
<CardTitle>Searching for Match</CardTitle>
21+
<CardDescription>
22+
Please wait while we find a suitable match for you.
23+
</CardDescription>
24+
</CardHeader>
25+
<CardContent>
26+
<div className="space-y-4">
27+
<Progress className="w-full" indeterminate />
28+
<div className="flex items-center justify-between">
29+
<div className="flex items-center space-x-2">
30+
<Clock className="h-4 w-4" />
31+
<span>
32+
Wait Time: {Math.floor(waitTime / 60)}:
33+
{(waitTime % 60).toString().padStart(2, "0")}
34+
</span>
35+
</div>
36+
<span>Searching...</span>
37+
</div>
38+
</div>
39+
</CardContent>
40+
</Card>
41+
);
42+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from "react";
2+
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
3+
import { AlertCircle } from "lucide-react";
4+
5+
interface SelectionSummaryProps {
6+
selectedDifficulty: string;
7+
selectedTopic: string;
8+
}
9+
10+
export function SelectionSummary({
11+
selectedDifficulty,
12+
selectedTopic,
13+
}: SelectionSummaryProps) {
14+
return (
15+
<Alert className="w-full max-w-2xl mx-auto mt-4">
16+
<AlertCircle className="h-4 w-4" />
17+
<AlertTitle>Selection Summary</AlertTitle>
18+
<AlertDescription>
19+
<p>Difficulty: {selectedDifficulty || "None selected"}</p>
20+
<p>Topic: {selectedTopic || "None selected"}</p>
21+
</AlertDescription>
22+
</Alert>
23+
);
24+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"use client";
2+
3+
import * as React from "react";
4+
import * as ProgressPrimitive from "@radix-ui/react-progress";
5+
6+
import { cn } from "@/lib/utils";
7+
8+
interface ProgressProps
9+
extends React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> {
10+
indeterminate?: boolean;
11+
}
12+
13+
const Progress = React.forwardRef<
14+
React.ElementRef<typeof ProgressPrimitive.Root>,
15+
ProgressProps
16+
>(({ className, value, indeterminate = false, ...props }, ref) => (
17+
<ProgressPrimitive.Root
18+
ref={ref}
19+
className={cn(
20+
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
21+
className
22+
)}
23+
{...props}
24+
>
25+
<ProgressPrimitive.Indicator
26+
className={cn(
27+
"h-full w-full flex-1 bg-primary transition-all",
28+
indeterminate && "animate-progress origin-left"
29+
)}
30+
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
31+
/>
32+
</ProgressPrimitive.Root>
33+
));
34+
Progress.displayName = ProgressPrimitive.Root.displayName;
35+
36+
export { Progress };

0 commit comments

Comments
 (0)