Skip to content

Commit a270e9b

Browse files
DinoxhCopilot
andauthored
Searchbar functionality (#21)
* added loader * added favourite bar, search function. * Update my-app/src/views/Components/FavouriteDropdown.jsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update my-app/src/model.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent cfd0575 commit a270e9b

File tree

9 files changed

+163
-50
lines changed

9 files changed

+163
-50
lines changed

.DS_Store

0 Bytes
Binary file not shown.

my-app/src/assets/project_icon.png

69.8 KB
Loading

my-app/src/model.js

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,27 @@
1-
// This model is representing our program logic.
2-
// A certain model is bound to a specific session.
3-
41
import { addCourse } from "../firebase";
52

63
export const model = {
74
user: undefined,
85
currentCourse: undefined,
9-
currentSearch: {},
6+
currentSearch: [],
107
courses: [],
8+
favourites: [],
119
isReady: false,
1210

1311
// sets the current user
1412
setUser(user) {
1513
if (!this.user)
1614
this.user = user;
1715
},
18-
16+
1917
// sets the currently selected course (detail view?) - could be component state
2018
setCurrentCourse(course){
2119
this.currentCourse = course;
2220
},
2321

2422
// keeps track of the current search / the associated promises.
25-
setCurrentSearch(search){
26-
this.currentSearch = search;
23+
setCurrentSearch(searchResults){
24+
this.currentSearch = searchResults;
2725
},
2826

2927
// sets the course array - for example after loading all courses from the DB
@@ -32,21 +30,23 @@ export const model = {
3230
},
3331

3432
// add a single course
35-
// addCourse(course){
36-
// this.courses = [...this.courses, course] // update local copy
37-
// addCourse(course); // update firebase
38-
// },
39-
4033
async addCourse(course) {
4134
try {
4235
await addCourse(course);
4336
this.courses = [...this.courses, course];
44-
console.log("Course added successfully.");
4537
} catch (error) {
4638
console.error("Error adding course:", error);
4739
}
4840
},
4941

42+
addFavourite(course) {
43+
this.favourites = [...this.favourites, course];
44+
},
45+
46+
removeFavourite(course) {
47+
this.favourites = (this.favourites || []).filter(fav => fav.code !== course.code);
48+
},
49+
5050
getCourse(courseID) {
5151
return this.courses.find(course => course.code === courseID);
5252
},
@@ -72,6 +72,14 @@ export const model = {
7272
};
7373
this.addCourse(course);
7474
});
75-
}
76-
}
75+
},
7776

77+
searchCourses(query) {
78+
const searchResults = this.courses.filter(course =>
79+
course.code.toLowerCase() === query.toLowerCase() ||
80+
course.name.toLowerCase().includes(query.toLowerCase()) ||
81+
course.description.toLowerCase().includes(query.toLowerCase())
82+
);
83+
this.setCurrentSearch(searchResults);
84+
}
85+
};

my-app/src/presenters/ListViewPresenter.jsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@ import React from 'react';
22
import { observer } from "mobx-react-lite";
33
import ListView from "../views/ListView.jsx";
44

5-
const ListViewPresenter = observer((props) => {
6-
return <ListView courses={props.model.courses} />;
5+
const ListViewPresenter = observer(({ model }) => {
6+
return <ListView
7+
courses={model.courses}
8+
searchResults={model.currentSearch}
9+
favouriteCourses={model.favourites}
10+
addFavourite={model.addFavourite}
11+
removeFavourite={model.removeFavourite}
12+
/>;
713
});
814

915
export { ListViewPresenter };

my-app/src/presenters/SearchbarPresenter.jsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,17 @@ import { observer } from "mobx-react-lite";
33
import SearchbarView from "../views/SearchbarView.jsx";
44

55
const SearchbarPresenter = observer(({ model }) => {
6+
const searchCourses = (query) => {
7+
model.searchCourses(query);
8+
}
9+
610
return (
7-
<SearchbarView courses = {model.courses} searchResults={model.setCurrentSearch} />
11+
<SearchbarView
12+
model={model}
13+
searchCourses={searchCourses}
14+
favouriteCourses = {model.favourites}
15+
removeFavourite={model.removeFavourite}
16+
/>
817
);
918
});
1019

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from 'react';
2+
3+
function FavouritesDropdown(props) {
4+
return (
5+
<div className=" absolute mt-2 w-48 bg-white border border-gray-300 rounded-lg">
6+
{props.favouriteCourses.length > 0 ? (
7+
props.favouriteCourses.map(course => (
8+
9+
<div
10+
key={course.code}
11+
className="p-2 flex justify-between items-center">
12+
13+
<p>{course.name}</p>
14+
15+
<button
16+
className="text-red-500 cursor-pointer"
17+
onClick={() => props.removeFavourite(course.code)}>
18+
X
19+
</button>
20+
21+
</div>
22+
))
23+
) :
24+
(
25+
<div className="p-2 text-gray-500">No favourites</div>
26+
)
27+
}
28+
</div>
29+
);
30+
}
31+
32+
export default FavouritesDropdown;

my-app/src/views/ListView.jsx

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,57 @@
1-
import React from 'react';
2-
import { Quantum } from 'ldrs/react'
3-
import 'ldrs/react/Quantum.css'
1+
import React, { useState } from 'react';
2+
import { Quantum } from 'ldrs/react';
3+
import 'ldrs/react/Quantum.css';
4+
import FavouritesDropdown from './Components/FavouriteDropdown.jsx';
45

56
function ListView(props) {
7+
const coursesToDisplay = props.searchResults.length > 0 ? props.searchResults : props.courses;
8+
const [readMoreState, setReadMoreState] = useState({});
9+
10+
const toggleReadMore = (courseCode) => {
11+
setReadMoreState(prevState => (
12+
{...prevState, [courseCode]: !prevState[courseCode]}));
13+
};
14+
15+
const handleFavouriteClick = (courseCode) => {
16+
if (props.favouriteCourses.includes(courseCode)) {
17+
props.removeFavourite(courseCode);
18+
}
19+
else {
20+
props.addFavourite(courseCode);
21+
}
22+
};
23+
624
return (
7-
<div className="bg-white text-black p-2 flex flex-col gap-5 h-full overflow-auto">
25+
<div className="relative bg-white text-black p-2 flex flex-col gap-5 h-full overflow-auto">
826
{
9-
props?.courses && props.courses.length > 0 ?
10-
props.courses.map((course) => (
27+
coursesToDisplay.length > 0 ?
28+
coursesToDisplay.map((course) => (
1129
<div
1230
key={course.code}
13-
className="p-5 hover:bg-[#000061] flex items-center cursor-pointer border border-b-black border-solid w-full rounded-lg">
31+
className="p-5 hover:bg-blue-100 flex items-center border border-b-black border-solid w-full rounded-lg cursor-pointer">
1432
<div>
1533
<p className={"font-bold text-[#000061]"}>{course.code}</p>
1634
<p className="font-bold">{course.name}</p>
17-
<p className="text-gray-600" dangerouslySetInnerHTML={{__html:course.description}}/>
35+
<p className="text-gray-600" dangerouslySetInnerHTML={
36+
{ __html: readMoreState[course.code] ? course.description : course.description.slice(0, 150) }
37+
}
38+
/>
39+
{course.description.length > 150 && (
40+
<span className="text-blue-500" onClick={() => toggleReadMore(course.code)}>
41+
{readMoreState[course.code] ? ' show less' : 'read more'}
42+
</span>
43+
)}
44+
45+
<div>
46+
<button className="text-yellow-500 cursor-pointer" onClick={() => handleFavouriteClick(course.code)}>
47+
{props.favouriteCourses.includes(course.code)
48+
?
49+
'Remove from Favourites'
50+
:
51+
'Add to Favourites'
52+
}
53+
</button>
54+
</div>
1855
</div>
1956
</div>
2057
)) :

my-app/src/views/SearchbarView.jsx

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
import React, { useState, useEffect } from 'react';
2-
import { model } from '../model.js';
3-
import kth_logo from '../assets/kth_logo.png';
42
import { getAuth, signInWithPopup, signOut, GoogleAuthProvider } from "firebase/auth";
3+
import kth_logo from '../assets/kth_logo.png';
4+
import project_logo from '../assets/project_icon.png';
5+
import FavouritesDropdown from './Components/FavouriteDropdown.jsx';
56

67
function SearchbarView(props) {
78
const [searchQuery, setSearchQuery] = useState('');
89
const [user, setUser] = useState(null);
10+
const [showFavourites, setShowFavourites] = useState(false);
11+
912
const auth = getAuth();
1013
const provider = new GoogleAuthProvider();
1114

1215
useEffect(() => {
13-
const unsubscribe = auth.onAuthStateChanged(setUser);
16+
const unsubscribe = auth.onAuthStateChanged((user) => {
17+
setUser(user);
18+
});
1419
return () => unsubscribe();
15-
},[auth]);
20+
}, [auth]);
1621

17-
const handleSearch = () => {
18-
const results = props.courses.filter(course =>
19-
course.name.toLowerCase().includes(searchQuery.toLowerCase())
20-
).splice(0, 10);
21-
props.searchResults(results);
22+
const handleSearch = (query) => {
23+
setSearchQuery(query);
24+
props.searchCourses(query);
2225
};
2326

2427
const handleSignIn = async () => {
@@ -37,7 +40,7 @@ function SearchbarView(props) {
3740
<div className="w-full px-6 py-6 bg-[#000061] flex items-center justify-between">
3841
<a href="https://www.kth.se" className="flex items-center h-[90px] w-auto">
3942
<img
40-
src={kth_logo}
43+
src={project_logo}
4144
className="h-[90px] w-auto"
4245
alt="KTH Logo"
4346
/>
@@ -47,36 +50,54 @@ function SearchbarView(props) {
4750
type="text"
4851
placeholder="What course are you looking for?"
4952
value={searchQuery}
50-
onChange={(e) => setSearchQuery(e.target.value)}
51-
onKeyPress={(e) => e.key === "Enter" && handleSearch()}
53+
onChange={(e) => handleSearch(e.target.value)}
5254
className="w-[400px] h-[44px] pl-14 pr-4 bg-white text-black rounded-full"
5355
/>
5456

55-
<div className="flex gap-4">
57+
<div className="flex gap-6 items-center">
5658
<button
57-
className="w-[100px] h-[44px] bg-white text-black rounded-full text-center border border-solid cursor-pointer hover:bg-[#000061] hover:text-white"
59+
className="w-[120px] h-[44px] bg-[#003399] text-white rounded-full border border-[#000061] cursor-pointer hover:bg-[#001a4d] transition-all duration-200"
5860
onClick={() => window.location.href = "https://inferencekth.github.io/Find-My-Next-Course/"}>
5961
About us
6062
</button>
6163

62-
<div className="w-[100px] h-[44px] bg-white text-black rounded-full text-center border border-solid cursor-pointer hover:bg-[#000061] hover:text-white flex items-center justify-center">
64+
<button onClick={() => setShowFavourites(!showFavourites)}
65+
className="w-[120px] h-[44px] bg-[#003399] text-white rounded-full border border-[#000061] cursor-pointer hover:bg-[#001a4d] transition-all duration-200">
66+
Favourites
67+
</button>
68+
69+
<div className="relative">
70+
{showFavourites && (
71+
<FavouritesDropdown
72+
favouriteCourses={props.favouriteCourses}
73+
removeFavourite={props.removeFavourite}
74+
/>
75+
)}
76+
</div>
77+
78+
<div className="flex items-center cursor-pointer">
6379
{user ? (
64-
<div className="flex items-center cursor-pointer">
65-
<img src={user.photoURL} alt="Profile" className="h-6 w-6 rounded-full mr-2" />
66-
<button
67-
onClick={handleSignOut}
68-
className="flex items-center justify-center w-full h-full curspor-pointer">
69-
Sign out
70-
</button>
71-
</div>
80+
<button
81+
onClick={handleSignOut}
82+
className="w-[120px] h-[44px] bg-[#003399] text-white rounded-full border border-[#000061] cursor-pointer hover:bg-[#001a4d] transition-all duration-200">
83+
Sign out
84+
</button>
7285
) : (
7386
<button
7487
onClick={handleSignIn}
75-
className="flex items-center justify-center w-full h-full cursor-pointer">
88+
className="w-auto min-w-[120px] h-[44px] bg-[#003399] text-white text-sm rounded-full border border-[#001a4d] cursor-pointer hover:bg-[#001a4d] transition-all duration-200 flex items-center justify-center px-4">
7689
Sign in with Google
7790
</button>
7891
)}
7992
</div>
93+
94+
{user && (
95+
<img
96+
src={user.photoURL}
97+
alt="Profile"
98+
className="w-[44px] h-[44px] rounded-full border border-[#000061]"
99+
/>
100+
)}
80101
</div>
81102
</div>
82103
);

src/.DS_Store

6 KB
Binary file not shown.

0 commit comments

Comments
 (0)