From 59160ac6f3e011dc7340cb98ff5556896190a5f4 Mon Sep 17 00:00:00 2001 From: bluesimp1102 Date: Mon, 11 Mar 2024 16:45:16 -0700 Subject: [PATCH 1/3] #94 add bug report button - Added bug report button in the main menu of the extension - Changes some styling to look more consistent --- src/App.tsx | 34 ++++++++++++++++++++++++---------- src/styles/App.css | 21 +++++++++++++++++++++ 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 4192d3d..461db8a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,7 @@ import { MsalProvider } from '@azure/msal-react'; import { msalInstance, MicrosoftOAuth } from './components/MicrosoftOath'; import { Box, IconButton } from '@mui/material'; import SettingsIcon from '@mui/icons-material/Settings'; +import BugReportIcon from '@mui/icons-material/BugReport'; import { Panel } from './components/panel_component'; import DegreeProgressBar from './components/DegreeProgressBar'; import './styles/App.css'; @@ -20,6 +21,14 @@ export function App(): ReactElement { const togglePanel = (): void => setPanelState(!isPanelOpen); const [isSettingsButtonOpen, setSettingsButtonState] = React.useState(true); + const handleBugReportClick = (): void => { + // Specify the URL you want to open in a new tab + const url = + 'https://docs.google.com/forms/d/e/1FAIpQLSfEAQ5xbzU98fxRBaQgxKv01pEU07_ALcrJU-lGmOdIhKvkAw/viewform'; + // Open a new tab with the specified URL + window.open(url, '_blank'); + }; + return (
@@ -30,17 +39,22 @@ export function App(): ReactElement {

BroncoDirectMe Search

- {isSettingsButtonOpen && ( - { - togglePanel(); - setSettingsButtonState(false); - }} - id="settingsButton" - > - +
+ + - )} + {isSettingsButtonOpen && ( + { + togglePanel(); + setSettingsButtonState(false); + }} + id="settingsButton" + > + + + )} +
{/* */} diff --git a/src/styles/App.css b/src/styles/App.css index ec73589..2380ad3 100644 --- a/src/styles/App.css +++ b/src/styles/App.css @@ -5,14 +5,35 @@ #mainContent { display: flex; justify-content: space-between; + align-items: center; padding-left: 5vw; padding-right: 5vw; } +#mainButtons { + display: flex; + align-items: center; + gap: 4px; +} + +#bugReportButton { + padding: 0; + border-radius: 6px; + height: fit-content; +} + +#bugReportIcon { + font-size: 2rem; + padding: 2px; +} + #settingsButton { padding: 0; + border-radius: 6px; + height: fit-content; } #settingsIcon { font-size: 2rem; + padding: 2px; } From 84161997e737fd936cfae74acb287b7c91e3b2e6 Mon Sep 17 00:00:00 2001 From: bluesimp1102 Date: Mon, 8 Apr 2024 18:16:40 -0700 Subject: [PATCH 2/3] Course Search Bar (#103) resolves #103 currently the course search bar is fetching data from backend running on localhost, remember to change to Production API URL when merging to prod branch --- src/components/CourseSearchBar.tsx | 159 +++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 src/components/CourseSearchBar.tsx diff --git a/src/components/CourseSearchBar.tsx b/src/components/CourseSearchBar.tsx new file mode 100644 index 0000000..f93d2a8 --- /dev/null +++ b/src/components/CourseSearchBar.tsx @@ -0,0 +1,159 @@ +import React, { useState, useEffect } from 'react'; +import { TextField, Autocomplete, CircularProgress } from '@mui/material'; + +interface CourseInfo { + id: string; + courseName: string; + courseNumber: string; + preReqs: string; + coReqs: string; + units: string; +} + +const CourseSearchBar: React.FC = () => { + const [open, setOpen] = useState(false); + const [searchText, setSearchText] = useState(''); + const [options, setOptions] = useState([]); + const [selectedCourse, setSelectedCourse] = useState(null); + const [loading, setLoading] = useState(false); + + useEffect(() => { + const delayDebounce = setTimeout(() => { + if (searchText === '') { + // setOptions([]); + return; + } + + setLoading(true); + fetch( + // change to prod api url when the backend endpoints are updated + // `https://api.cppbroncodirect.me/courses?key=${encodeURIComponent(searchText))}` + `http://localhost:3000/courses?key=${encodeURIComponent(searchText)}` + ) + .then(async (response) => await response.json()) + .then((data) => { + setOptions(data); + }) + .catch((error) => { + console.error(`Error fetching courses:`, error); + }) + .finally(() => { + setLoading(false); + }); + }, 2000); // 2-second delay + + return () => clearTimeout(delayDebounce); + }, [searchText]); + + const fetchCourseDetails = async (courseNumber: string): Promise => { + try { + const response = await fetch( + // change to prod api url when the backend endpoints are updated + // `https://api.cppbroncodirect.me/courses/${courseNumber}` + `http://localhost:3000/courses/${courseNumber}` + ); + if (!response.ok) { + throw new Error('Network response was not ok'); + } + const data: CourseInfo = await response.json(); + setSelectedCourse(data); + } catch (error) { + console.error('Error fetching course details:', error); + setSelectedCourse(null); + } + }; + + return ( +
+ { + setOpen(true); + }} + onClose={() => { + setOpen(false); + }} + getOptionLabel={(option) => + `${option.courseNumber}: ${option.courseName}` + } + options={options} + loading={loading} + onChange={(event, newValue: CourseInfo | null) => { + if (newValue) { + fetchCourseDetails(newValue.courseNumber).catch((e) => {}); + } + }} + renderInput={(params) => ( + setSearchText(e.target.value)} + placeholder="Search for a course" + variant="outlined" + InputProps={{ + ...params.InputProps, + endAdornment: ( + <> + {loading && } + {params.InputProps.endAdornment} + + ), + }} + /> + )} + /> + {selectedCourse && ( +
+

{selectedCourse.courseName}

+ + {( + [ + { name: 'Course ID', value: selectedCourse.courseNumber }, + { name: 'Units', value: selectedCourse.units }, + { + name: 'Prerequisites', + value: selectedCourse.preReqs, + hidden: !selectedCourse.preReqs, + }, + { + name: 'Corequisites', + value: selectedCourse.coReqs, + hidden: !selectedCourse.coReqs, + }, + ] as CourseFieldProps[] + ).map((field, index) => ( + + ))} + +
+ )} +
+ ); +}; + +export default CourseSearchBar; + +interface CourseFieldProps { + name: string; + value: string; + hidden?: boolean; +} +const CourseField: React.FC = ({ + name, + value, + hidden = false, +}) => { + return hidden ? null : ( +

+ {name}: {value} +

+ ); +}; From 15707dcf36d6d3dad777b428a358199cf6ed23f4 Mon Sep 17 00:00:00 2001 From: bluesimp1102 Date: Mon, 8 Apr 2024 18:17:33 -0700 Subject: [PATCH 3/3] Added Bottom Navigation (#141) resolves #141 Added bottom navigation for the extension --- public/index.html | 2 +- src/App.tsx | 113 +++++++++++++++++++++++++-------------- src/styles/App.css | 20 +++++-- src/styles/SearchBar.css | 9 ++-- 4 files changed, 94 insertions(+), 50 deletions(-) diff --git a/public/index.html b/public/index.html index 2899d0d..78beb3f 100644 --- a/public/index.html +++ b/public/index.html @@ -6,7 +6,7 @@ BroncoDirectMe - +
diff --git a/src/App.tsx b/src/App.tsx index 461db8a..e9663ad 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,22 +4,35 @@ import SearchBar from './components/SearchBar'; import { MsalProvider } from '@azure/msal-react'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { msalInstance, MicrosoftOAuth } from './components/MicrosoftOath'; -import { Box, IconButton } from '@mui/material'; +import { + Box, + IconButton, + BottomNavigation, + BottomNavigationAction, + Tooltip, +} from '@mui/material'; import SettingsIcon from '@mui/icons-material/Settings'; import BugReportIcon from '@mui/icons-material/BugReport'; -import { Panel } from './components/panel_component'; import DegreeProgressBar from './components/DegreeProgressBar'; import './styles/App.css'; import UpdateAlert from './components/UpdateAlert'; import TermsOfService from './components/TermsOfService'; +import CourseSearchBar from './components/CourseSearchBar'; +import HomeIcon from '@mui/icons-material/Home'; +import SearchIcon from '@mui/icons-material/Search'; /** * @returns Main app component */ export function App(): ReactElement { - const [isPanelOpen, setPanelState] = React.useState(false); - const togglePanel = (): void => setPanelState(!isPanelOpen); - const [isSettingsButtonOpen, setSettingsButtonState] = React.useState(true); + const [value, setValue] = React.useState('home'); + + const handleChange = ( + event: React.SyntheticEvent, + newValue: string + ): void => { + setValue(newValue); + }; const handleBugReportClick = (): void => { // Specify the URL you want to open in a new tab @@ -32,46 +45,64 @@ export function App(): ReactElement { return (
- {!isPanelOpen && ( -
- - -
- -

BroncoDirectMe Search

-
+
+ + +
+ +

BroncoDirectMe

+
+ - {isSettingsButtonOpen && ( - { - togglePanel(); - setSettingsButtonState(false); - }} - id="settingsButton" - > - - - )} -
-
- - {/* */} - -
- )} - {/* Hides main app components when setting panel opens */} - { - togglePanel(); - setSettingsButtonState(true); - }} + +
+
+ {value === 'home' && ( + <> + + + + )} + + {value === 'search' && ( + <> + + {/* Additional components for Search Courses tab */} + + )} + + {/* Settings components */} + {value === 'settings' && ( + <> + + + )} + {/* */} +
+ - - + } + /> + } + /> + } + /> +
); diff --git a/src/styles/App.css b/src/styles/App.css index 2380ad3..4e0f92b 100644 --- a/src/styles/App.css +++ b/src/styles/App.css @@ -1,13 +1,20 @@ .App { - min-height: 250px; + height: 400px; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.App section { + padding: 3vh 5vw; + overflow-y: auto; + flex-grow: 1; } #mainContent { display: flex; justify-content: space-between; align-items: center; - padding-left: 5vw; - padding-right: 5vw; } #mainButtons { @@ -25,6 +32,7 @@ #bugReportIcon { font-size: 2rem; padding: 2px; + color: #bf0a30; } #settingsButton { @@ -36,4 +44,10 @@ #settingsIcon { font-size: 2rem; padding: 2px; + color: #2b7dff; +} + +#bottomNavBar { + border-top: solid 1px #989898b0; + flex-shrink: 0; } diff --git a/src/styles/SearchBar.css b/src/styles/SearchBar.css index bd9e362..5141567 100644 --- a/src/styles/SearchBar.css +++ b/src/styles/SearchBar.css @@ -50,15 +50,14 @@ } .search-bar { - width: 85vw; - margin-bottom: 10%; - margin-left: 5vw; - box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + width: 100%; + margin-bottom: 3vh; } .loading-container { display: flex; - justify-content: space-between; + justify-content: center; + align-items: center; margin-bottom: 20px; }