diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..0436f8ec --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "findmyprofessors", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/web/public/index.html b/web/public/index.html index dfa28bd0..1b685ca4 100644 --- a/web/public/index.html +++ b/web/public/index.html @@ -33,7 +33,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - MDBReact5 Template App + Find My Professors diff --git a/web/src/components/Dashboard.jsx b/web/src/components/Dashboard.jsx index eeeb017e..17537e13 100644 --- a/web/src/components/Dashboard.jsx +++ b/web/src/components/Dashboard.jsx @@ -18,6 +18,10 @@ const Dashboard = () => { const [professorsData, setProfessorsData] = useState([]); const [headersVisible, setHeadersVisible] = useState(true); const [showSuggestions, setShowSuggestions] = useState(true); + const [courseId, setCourseId] = useState(''); + const [cartVisible, setCartVisible] = useState(false); + const [cartItems, setCartItems] = useState([]); + const [addClassMessage, setAddClassMessage] = useState(''); const preventClose = (e) => { e.stopPropagation(); @@ -98,6 +102,7 @@ const Dashboard = () => { const fetchProfessors = async (courseId, year, semester) => { const token = localStorage.getItem('token'); + localStorage.setItem('course_id', courseId); if (token) { try { @@ -106,6 +111,7 @@ const Dashboard = () => { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } + }); if (response.ok) { @@ -128,6 +134,7 @@ const Dashboard = () => { } }; + const fetchCourses = async (schoolId, year, semester, query) => { const token = localStorage.getItem('token'); @@ -248,6 +255,71 @@ const Dashboard = () => { } }; + const handleCartClick = async () => { + setCartVisible(!cartVisible); + if (!cartVisible) { + const userId = localStorage.getItem('user_id'); + if (userId) { + try { + const response = await fetch(`${API_URL}/users/${userId}/cart`, { + headers: { + 'Authorization': `Bearer ${localStorage.getItem('token')}`, + 'Content-Type': 'application/json' + } + }); + + if (response.ok) { + const data = await response.json(); + setCartItems(data.entries.map(entry => ({ + firstName: entry.professor.first_name, + lastName: entry.professor.last_name, + code: entry.course.code, + courseName: entry.course.name, + professorId: entry.professor.id, + courseId: entry.course.id + }))); + } else { + console.error('Failed to fetch cart data:', response.statusText); + } + } catch (error) { + console.error('Error fetching cart data:', error); + } + } else { + console.error('No user ID found'); + } + } + }; + + const handleDeleteClick = async (professorId, courseId) => { + if (window.confirm('Are you sure you want to delete this entry?')) { + const userId = localStorage.getItem('user_id'); + const token = localStorage.getItem('token'); + + if (userId && token) { + try { + const response = await fetch(`${API_URL}/users/${userId}/cart`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ professor_id: professorId, course_id: courseId }) + }); + + if (response.ok) { + setCartItems((prevItems) => prevItems.filter(item => !(item.professorId === professorId && item.courseId === courseId))); + } else { + console.error('Failed to delete item from cart:', response.statusText); + } + } catch (error) { + console.error('Error deleting item from cart:', error); + } + } else { + console.error('No user ID or token found'); + } + } + }; + return ( <>
@@ -255,7 +327,6 @@ const Dashboard = () => { -
{headersVisible && ( @@ -289,9 +360,8 @@ const Dashboard = () => { 2024 {year === 2024 && } handleDropdownClick('year', 2025)}> - 2025 {year === 2025 && } - - + 2025 {year === 2025 && } + @@ -317,6 +387,9 @@ const Dashboard = () => { + + Cart + {showSuggestions && searchResults.length > 0 && ( @@ -331,19 +404,38 @@ const Dashboard = () => {
- {!headersVisible && professorsData.length > 0 && ( + {addClassMessage && ( +
+ {addClassMessage} +
+ )} + + {cartVisible && (
- + setCartVisible(false)}> + Go Back to Professors + +
)} + {!headersVisible && professorsData.length > 0 && !cartVisible && ( +
+ +
+ )}
); }; -const ProfessorTable = ({ professors, fetchProfessorRatings, fetchProfessorAnalysis }) => { +const ProfessorTable = ({ professors, fetchProfessorRatings, fetchProfessorAnalysis, setAddClassMessage }) => { const [searchTerm, setSearchTerm] = useState(''); const [currentPage, setCurrentPage] = useState(1); const [selectedProfessor, setSelectedProfessor] = useState(null); @@ -381,24 +473,27 @@ const ProfessorTable = ({ professors, fetchProfessorRatings, fetchProfessorAnaly })); }; - const handleAddClick = async (event, professorId, courseId) => { + const handleAddClick = async (event, professor) => { event.stopPropagation(); // Prevent the event from propagating to the row click const token = localStorage.getItem('token'); const userId = localStorage.getItem('user_id'); + const courseId = localStorage.getItem('course_id'); if (token && userId) { try { - console.log('Sending request with data:', { professorId, courseId }); // Debugging log + console.log('Sending request with data:', { professorId: professor.id, courseId }); // Debugging log const response = await fetch(`${API_URL}/users/${userId}/cart`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, - body: JSON.stringify({ professor_id: professorId, course_id: courseId}) + body: JSON.stringify({ professor_id: professor.id, course_id: courseId }) }); if (response.ok) { + setAddClassMessage(`Class added: ${professor.first_name} ${professor.last_name}`); + setTimeout(() => setAddClassMessage(''), 3000); console.log('Professor added to cart successfully'); } else { console.error('Failed to add professor to cart:', response.statusText); @@ -447,7 +542,7 @@ const ProfessorTable = ({ professors, fetchProfessorRatings, fetchProfessorAnaly {ratingsData[professor.id] ? roundToTenth(ratingsData[professor.id].totalQualityAverage) : '-'} {ratingsData[professor.id]?.ratingAmount || '-'} - handleAddClick(event, professor.id)}>Add + handleAddClick(event, professor)}>Add {selectedProfessor === professor && ( @@ -484,13 +579,44 @@ const ProfessorTable = ({ professors, fetchProfessorRatings, fetchProfessorAnaly ); }; +const CartTable = ({ cartItems, handleDeleteClick }) => { + return ( + + + + First Name + Last Name + Code + Course Name + Delete + + + + {cartItems.map((item, index) => ( + + {item.firstName} + {item.lastName} + {item.code} + {item.courseName} + + handleDeleteClick(item.professorId, item.courseId)}> + Delete + + + + ))} + + + ); +}; + const ProfessorDetails = ({ professor, analysisData }) => { const lineData = { - labels: analysisData ? analysisData.averageRatingValues.map(item => `${item.month} ${item.year}`) : [], + labels: analysisData ? analysisData.averageRatingValues.map(item => `${item.month} ${item.year}`).reverse() : [], datasets: [ { label: 'Rating Over Time', - data: analysisData ? analysisData.averageRatingValues.map(item => item.value) : [], + data: analysisData ? analysisData.averageRatingValues.map(item => item.value).reverse() : [], fill: false, backgroundColor: 'rgb(75, 192, 192)', borderColor: 'rgba(75, 192, 192, 0.2)', diff --git a/web/src/components/DashboardHeader.jsx b/web/src/components/DashboardHeader.jsx index 0f99dc8e..b169adc3 100644 --- a/web/src/components/DashboardHeader.jsx +++ b/web/src/components/DashboardHeader.jsx @@ -139,7 +139,9 @@ const DashboardHeader = ({ onSearch }) => { - Cart + Cart diff --git a/web/src/components/EmailConfirmation.jsx b/web/src/components/EmailConfirmation.jsx index b5105738..707de3ef 100644 --- a/web/src/components/EmailConfirmation.jsx +++ b/web/src/components/EmailConfirmation.jsx @@ -59,10 +59,6 @@ const EmailConfirmation = () => {
Your email has been confirmed. Redirecting to login ...
-
- Terms of use. - Privacy policy -
diff --git a/web/src/components/ForgotPassword.jsx b/web/src/components/ForgotPassword.jsx index 87a099c8..e5f2cfb0 100644 --- a/web/src/components/ForgotPassword.jsx +++ b/web/src/components/ForgotPassword.jsx @@ -97,10 +97,6 @@ const ForgotPassword = () => { Send Reset Link -
- Terms of use. - Privacy policy -
diff --git a/web/src/components/Header.jsx b/web/src/components/Header.jsx index e3518e36..372ed03c 100644 --- a/web/src/components/Header.jsx +++ b/web/src/components/Header.jsx @@ -41,7 +41,11 @@ function Header() const navigate = useNavigate(); + const userId = localStorage.getItem('user_id'); + const handleLogout = () => { + localStorage.removeItem('user_id'); + console.log("userID is now " + userId); toggleOpen(); navigate('/Login'); }; @@ -108,13 +112,15 @@ function Header() + href='./Cart' + style={{ color: '#FFFFFF' }}> Cart
- + @@ -118,7 +118,7 @@ const containerStyle = { diff --git a/web/src/components/Login.jsx b/web/src/components/Login.jsx index a96f8186..0a938a6e 100644 --- a/web/src/components/Login.jsx +++ b/web/src/components/Login.jsx @@ -77,7 +77,7 @@ const Login = () => { <>
- + @@ -169,24 +169,12 @@ const Login = () => {

- Don't have an account? Register + style={{ color: 'white' }}> + Don't have an account? Register

- + diff --git a/web/src/components/Register.jsx b/web/src/components/Register.jsx index e92c1f8f..d889ec93 100644 --- a/web/src/components/Register.jsx +++ b/web/src/components/Register.jsx @@ -211,14 +211,10 @@ const Register = () => {

+ style={{ color: 'white' }}> Already have an account? Login

- diff --git a/web/src/components/ResetPassword.jsx b/web/src/components/ResetPassword.jsx index 92e4a8bf..855fc64b 100644 --- a/web/src/components/ResetPassword.jsx +++ b/web/src/components/ResetPassword.jsx @@ -140,10 +140,6 @@ const ResetPassword = () => { Reset Password - diff --git a/web/src/components/SearchBar.jsx b/web/src/components/SearchBar.jsx index d34ba143..397bc0dc 100644 --- a/web/src/components/SearchBar.jsx +++ b/web/src/components/SearchBar.jsx @@ -184,11 +184,15 @@ const SearchBar = ({ onSearch, filters, setFilters, preventClose, getYearText, g labelClass="text-black" style={{ backgroundColor: '#3f3f3f', boxShadow: '3px 3px 12px rgba(0, 0, 0, 0.75)' }} contrast + aria-label="Search Courses" label="Search Courses" value={query} onChange={(e) => setFilters({ ...filters, query: e.target.value })}/> - + diff --git a/web/src/components/VerifyEmail.jsx b/web/src/components/VerifyEmail.jsx index 2a0a5a1b..fa32e5e4 100644 --- a/web/src/components/VerifyEmail.jsx +++ b/web/src/components/VerifyEmail.jsx @@ -75,22 +75,6 @@ const VerifyEmail = () => { -