diff --git a/src/components/dashboard/Tutorial/tutorial.tsx b/src/components/dashboard/Tutorial/tutorial.tsx new file mode 100644 index 00000000..e53a9649 --- /dev/null +++ b/src/components/dashboard/Tutorial/tutorial.tsx @@ -0,0 +1,209 @@ +import type { ReactJSXElement } from '@emotion/react/types/jsx-namespace'; +import CloseIcon from '@mui/icons-material/Close'; +import { Backdrop, Button, IconButton, Popover } from '@mui/material'; +import React, { useEffect, useState } from 'react'; + +type TutorialPopupProps = { + element: Element; + open: boolean; + incrementStep: () => void; + close: () => void; + title: string; + buttonText: string; + anchorOrigin: { + vertical: 'center' | 'bottom' | 'top'; + horizontal: 'center' | 'left' | 'right'; + }; + transformOrigin: { + vertical: 'center' | 'bottom' | 'top'; + horizontal: 'center' | 'left' | 'right'; + }; + children: ReactJSXElement | string; +}; + +const TutorialPopup = ({ + element, + open, + incrementStep, + close, + title, + buttonText, + anchorOrigin, + transformOrigin, + children, +}: TutorialPopupProps) => { + useEffect(() => { + if (open) { + element.classList.add('tutorial-raise'); + if (element.tagName === 'TR' || element.tagName === 'TD') { + element.classList.add('tutorial-table'); + } + //Wait to scroll untill classes applies + setTimeout( + () => element.scrollIntoView({ behavior: 'smooth', block: 'nearest' }), + 0, + ); + } else { + element.classList.remove('tutorial-raise'); + element.classList.remove('tutorial-table'); + } + return () => { + element.classList.remove('tutorial-raise'); + element.classList.remove('tutorial-table'); + }; + }, [open, element]); + + return ( + +
+
+

{title}

+ + + +
+
{children}
+ +
+
+ ); +}; + +type StepTemplate = { + id: string; + element?: Element; + title: string; + content: ReactJSXElement | string; + anchorOrigin: { + vertical: 'center' | 'bottom' | 'top'; + horizontal: 'center' | 'left' | 'right'; + }; + transformOrigin: { + vertical: 'center' | 'bottom' | 'top'; + horizontal: 'center' | 'left' | 'right'; + }; +}; +type Step = StepTemplate & { element: Element }; + +const stepsTemplate: StepTemplate[] = [ + { + id: 'search', + title: 'Search', + content: + "Search for any number of courses and professors and we'll find all the combinations of classes they teach.", + anchorOrigin: { vertical: 'bottom', horizontal: 'center' }, + transformOrigin: { vertical: 'top', horizontal: 'center' }, + }, + { + id: 'RHS', + title: 'Overviews and Compare', + content: 'See an overview of your search on this side.', + anchorOrigin: { vertical: 'top', horizontal: 'left' }, + transformOrigin: { vertical: 'bottom', horizontal: 'right' }, + }, + { + id: 'result', + title: 'Results', + content: + 'See the average grade for a course and Rate My Professors score here.', + anchorOrigin: { vertical: 'bottom', horizontal: 'center' }, + transformOrigin: { vertical: 'top', horizontal: 'center' }, + }, + { + id: 'dropdown', + title: 'More information', + content: 'Open a result for more detailed information.', + anchorOrigin: { vertical: 'bottom', horizontal: 'center' }, + transformOrigin: { vertical: 'top', horizontal: 'center' }, + }, + { + id: 'compare', + title: 'Compare', + content: 'Click the checkbox to add an item to the compare tab.', + anchorOrigin: { vertical: 'bottom', horizontal: 'center' }, + transformOrigin: { vertical: 'top', horizontal: 'center' }, + }, + { + id: 'LHS', + title: "That's all!", + content: + 'Try searching for a class you need to take and looking through the results.', + anchorOrigin: { vertical: 'top', horizontal: 'right' }, + transformOrigin: { vertical: 'bottom', horizontal: 'left' }, + }, +]; + +type TutorialProps = { + open: boolean; + close: () => void; +}; + +const Tutorial = ({ open, close }: TutorialProps) => { + const [steps, setSteps] = useState([]); + const [place, setPlace] = useState(0); + + useEffect(() => { + // For each element, set anchor based on `data-tutorial-id` + const elements = document.querySelectorAll('[data-tutorial-id]'); + if (!elements.length) { + close(); + return; + } + const newSteps = [...stepsTemplate]; + elements.forEach((element) => { + const id = element.getAttribute('data-tutorial-id') as string; + const foundStep = newSteps.findIndex((step) => step.id === id); + if (foundStep !== -1) { + newSteps[foundStep].element = element; + } + }); + setSteps( + newSteps.filter((step) => typeof step.element !== 'undefined') as Step[], + ); + setPlace(0); + }, [open, close]); + + return ( + <> + ({ zIndex: theme.zIndex.modal })} open={open} /> + {steps.map(({ content, ...otherProps }, index) => ( + { + if (place === steps.length - 1) { + close(); + return; + } + setPlace(place + 1); + }} + close={close} + buttonText={index === steps.length - 1 ? 'Done' : 'Next'} + {...otherProps} + > + {content} + + ))} + + ); +}; + +export default Tutorial; diff --git a/src/components/navigation/topMenu/topMenu.tsx b/src/components/navigation/topMenu/topMenu.tsx index 47697305..949cef84 100644 --- a/src/components/navigation/topMenu/topMenu.tsx +++ b/src/components/navigation/topMenu/topMenu.tsx @@ -1,11 +1,13 @@ -import { Share } from '@mui/icons-material'; +import QuestionMarkIcon from '@mui/icons-material/QuestionMark'; +import ShareIcon from '@mui/icons-material/Share'; import { IconButton, Snackbar, Tooltip, useMediaQuery } from '@mui/material'; import Image from 'next/image'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; import Background from '@/../public/background.png'; +import Tutorial from '@/components/dashboard/Tutorial/tutorial'; import SearchBar from '@/components/search/SearchBar/searchBar'; /** @@ -51,6 +53,9 @@ export function TopMenu() { /> ); + const [openTutorial, setOpenTutorial] = useState(false); + const closeTutorial = useCallback(() => setOpenTutorial(false), []); + return ( <>
{matches && searchBar} - - { - let url = window.location.href; - if ( - router.query && - Object.keys(router.query).length === 0 && - Object.getPrototypeOf(router.query) === Object.prototype - ) { - url = 'https://trends.utdnebula.com/'; - } - shareLink(url); - }} - > - - - +
+ + { + setOpenTutorial(true); + }} + > + + + + + { + let url = window.location.href; + if ( + router.query && + Object.keys(router.query).length === 0 && + Object.getPrototypeOf(router.query) === Object.prototype + ) { + url = 'https://trends.utdnebula.com/'; + } + shareLink(url); + }} + > + + + +
{!matches && searchBar}
setOpenCopied(false)} message="Copied!" /> + ); } diff --git a/src/components/search/SearchBar/searchBar.tsx b/src/components/search/SearchBar/searchBar.tsx index b931d00a..c35774ae 100644 --- a/src/components/search/SearchBar/searchBar.tsx +++ b/src/components/search/SearchBar/searchBar.tsx @@ -211,7 +211,10 @@ const SearchBar = ({ }, []); return ( -
+
void; removeFromCompare: (arg0: SearchQuery) => void; color?: string; + showTutorial: boolean; }; function Row({ @@ -96,6 +97,7 @@ function Row({ addToCompare, removeFromCompare, color, + showTutorial, }: RowProps) { const [open, setOpen] = useState(false); @@ -120,8 +122,12 @@ function Row({ setOpen(!open)} // opens/closes the card by clicking anywhere on the row className="cursor-pointer" + data-tutorial-id={showTutorial && 'result'} > - + - + }; rmp: { [key: string]: GenericFetchedData }; @@ -263,6 +273,7 @@ type SearchResultsTableProps = { const SearchResultsTable = ({ resultsLoading, + numSearches, includedResults, grades, rmp, @@ -495,7 +506,7 @@ const SearchResultsTable = ({ {resultsLoading === 'done' - ? sortedResults.map((result) => ( + ? sortedResults.map((result, index) => ( )) : Array(10) diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 23313a7b..884007e7 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -79,6 +79,7 @@ const kallisto = localFont({ function MyApp({ Component, pageProps }: AppProps) { const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); const muiTheme = createTheme({ + cssVariables: true, palette: { mode: prefersDarkMode ? 'dark' : 'light', //copied from tailwind.config.js diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx index e31a14c4..d4a178d8 100644 --- a/src/pages/dashboard/index.tsx +++ b/src/pages/dashboard/index.tsx @@ -774,9 +774,10 @@ export const Dashboard: NextPage = () => { - + { colorMap={colorMap} /> - +
diff --git a/src/styles/globals.css b/src/styles/globals.css index 265750b2..7d3a5fc0 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -27,4 +27,13 @@ body, .bg-darken { background-color: rgba(0, 0, 0, 0.6); } + + .tutorial-raise { + position: relative; + z-index: calc(var(--mui-zIndex-modal) + 1); + scroll-margin: 10rem; + } + .tutorial-table { + background-color: var(--mui-palette-background-paper); + } }