diff --git a/.DS_Store b/.DS_Store
index 0cd3991b..59a8781c 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/my-app/src/assets/project_icon.png b/my-app/src/assets/project_icon.png
new file mode 100644
index 00000000..898e331e
Binary files /dev/null and b/my-app/src/assets/project_icon.png differ
diff --git a/my-app/src/model.js b/my-app/src/model.js
index f2a67e9e..4a2e0cf2 100644
--- a/my-app/src/model.js
+++ b/my-app/src/model.js
@@ -1,13 +1,11 @@
-// This model is representing our program logic.
-// A certain model is bound to a specific session.
-
import { addCourse } from "../firebase";
export const model = {
user: undefined,
currentCourse: undefined,
- currentSearch: {},
+ currentSearch: [],
courses: [],
+ favourites: [],
isReady: false,
// sets the current user
@@ -15,15 +13,15 @@ export const model = {
if (!this.user)
this.user = user;
},
-
+
// sets the currently selected course (detail view?) - could be component state
setCurrentCourse(course){
this.currentCourse = course;
},
// keeps track of the current search / the associated promises.
- setCurrentSearch(search){
- this.currentSearch = search;
+ setCurrentSearch(searchResults){
+ this.currentSearch = searchResults;
},
// sets the course array - for example after loading all courses from the DB
@@ -32,21 +30,23 @@ export const model = {
},
// add a single course
- // addCourse(course){
- // this.courses = [...this.courses, course] // update local copy
- // addCourse(course); // update firebase
- // },
-
async addCourse(course) {
try {
await addCourse(course);
this.courses = [...this.courses, course];
- console.log("Course added successfully.");
} catch (error) {
console.error("Error adding course:", error);
}
},
+ addFavourite(course) {
+ this.favourites = [...this.favourites, course];
+ },
+
+ removeFavourite(course) {
+ this.favourites = (this.favourites || []).filter(fav => fav.code !== course.code);
+ },
+
getCourse(courseID) {
return this.courses.find(course => course.code === courseID);
},
@@ -71,6 +71,14 @@ export const model = {
};
this.addCourse(course);
});
- }
-}
+ },
+ searchCourses(query) {
+ const searchResults = this.courses.filter(course =>
+ course.code.toLowerCase() === query.toLowerCase() ||
+ course.name.toLowerCase().includes(query.toLowerCase()) ||
+ course.description.toLowerCase().includes(query.toLowerCase())
+ );
+ this.setCurrentSearch(searchResults);
+ }
+};
\ No newline at end of file
diff --git a/my-app/src/presenters/ListViewPresenter.jsx b/my-app/src/presenters/ListViewPresenter.jsx
index 500a05c3..b6a50f9e 100644
--- a/my-app/src/presenters/ListViewPresenter.jsx
+++ b/my-app/src/presenters/ListViewPresenter.jsx
@@ -2,8 +2,14 @@ import React from 'react';
import { observer } from "mobx-react-lite";
import ListView from "../views/ListView.jsx";
-const ListViewPresenter = observer((props) => {
- return ;
+const ListViewPresenter = observer(({ model }) => {
+ return ;
});
export { ListViewPresenter };
\ No newline at end of file
diff --git a/my-app/src/presenters/SearchbarPresenter.jsx b/my-app/src/presenters/SearchbarPresenter.jsx
index f2cd9091..dbca139e 100644
--- a/my-app/src/presenters/SearchbarPresenter.jsx
+++ b/my-app/src/presenters/SearchbarPresenter.jsx
@@ -3,8 +3,17 @@ import { observer } from "mobx-react-lite";
import SearchbarView from "../views/SearchbarView.jsx";
const SearchbarPresenter = observer(({ model }) => {
+ const searchCourses = (query) => {
+ model.searchCourses(query);
+ }
+
return (
-
+
);
});
diff --git a/my-app/src/views/Components/FavouriteDropdown.jsx b/my-app/src/views/Components/FavouriteDropdown.jsx
new file mode 100644
index 00000000..973c558c
--- /dev/null
+++ b/my-app/src/views/Components/FavouriteDropdown.jsx
@@ -0,0 +1,32 @@
+import React from 'react';
+
+function FavouritesDropdown(props) {
+ return (
+
+ {props.favouriteCourses.length > 0 ? (
+ props.favouriteCourses.map(course => (
+
+
+
+
{course.name}
+
+
+
+
+ ))
+ ) :
+ (
+
No favourites
+ )
+ }
+
+ );
+}
+
+export default FavouritesDropdown;
\ No newline at end of file
diff --git a/my-app/src/views/ListView.jsx b/my-app/src/views/ListView.jsx
index 5a7213f5..9acbeaec 100644
--- a/my-app/src/views/ListView.jsx
+++ b/my-app/src/views/ListView.jsx
@@ -1,20 +1,57 @@
-import React from 'react';
-import { Quantum } from 'ldrs/react'
-import 'ldrs/react/Quantum.css'
+import React, { useState } from 'react';
+import { Quantum } from 'ldrs/react';
+import 'ldrs/react/Quantum.css';
+import FavouritesDropdown from './Components/FavouriteDropdown.jsx';
function ListView(props) {
+ const coursesToDisplay = props.searchResults.length > 0 ? props.searchResults : props.courses;
+ const [readMoreState, setReadMoreState] = useState({});
+
+ const toggleReadMore = (courseCode) => {
+ setReadMoreState(prevState => (
+ {...prevState, [courseCode]: !prevState[courseCode]}));
+ };
+
+ const handleFavouriteClick = (courseCode) => {
+ if (props.favouriteCourses.includes(courseCode)) {
+ props.removeFavourite(courseCode);
+ }
+ else {
+ props.addFavourite(courseCode);
+ }
+ };
+
return (
-
+
{
- props?.courses && props.courses.length > 0 ?
- props.courses.map((course) => (
+ coursesToDisplay.length > 0 ?
+ coursesToDisplay.map((course) => (
+ className="p-5 hover:bg-blue-100 flex items-center border border-b-black border-solid w-full rounded-lg cursor-pointer">
{course.code}
{course.name}
-
+
+ {course.description.length > 150 && (
+
toggleReadMore(course.code)}>
+ {readMoreState[course.code] ? ' show less' : 'read more'}
+
+ )}
+
+
+
+
)) :
diff --git a/my-app/src/views/SearchbarView.jsx b/my-app/src/views/SearchbarView.jsx
index 6fc31899..d9fb1bbd 100644
--- a/my-app/src/views/SearchbarView.jsx
+++ b/my-app/src/views/SearchbarView.jsx
@@ -1,24 +1,27 @@
import React, { useState, useEffect } from 'react';
-import { model } from '../model.js';
-import kth_logo from '../assets/kth_logo.png';
import { getAuth, signInWithPopup, signOut, GoogleAuthProvider } from "firebase/auth";
+import kth_logo from '../assets/kth_logo.png';
+import project_logo from '../assets/project_icon.png';
+import FavouritesDropdown from './Components/FavouriteDropdown.jsx';
function SearchbarView(props) {
const [searchQuery, setSearchQuery] = useState('');
const [user, setUser] = useState(null);
+ const [showFavourites, setShowFavourites] = useState(false);
+
const auth = getAuth();
const provider = new GoogleAuthProvider();
useEffect(() => {
- const unsubscribe = auth.onAuthStateChanged(setUser);
+ const unsubscribe = auth.onAuthStateChanged((user) => {
+ setUser(user);
+ });
return () => unsubscribe();
- },[auth]);
+ }, [auth]);
- const handleSearch = () => {
- const results = props.courses.filter(course =>
- course.name.toLowerCase().includes(searchQuery.toLowerCase())
- ).splice(0, 10);
- props.searchResults(results);
+ const handleSearch = (query) => {
+ setSearchQuery(query);
+ props.searchCourses(query);
};
const handleSignIn = async () => {
@@ -37,7 +40,7 @@ function SearchbarView(props) {