From 7047e61c8fdb81b4cec64a789af00c85b9cec892 Mon Sep 17 00:00:00 2001 From: Tyler Hill Date: Fri, 1 Nov 2024 01:08:05 -0500 Subject: [PATCH 1/8] Step by step tutorial --- .../SearchResultsTable/searchResultsTable.tsx | 4 +- src/components/common/Tutorial/tutorial.tsx | 112 ++++++++++++++++++ src/components/navigation/topMenu/topMenu.tsx | 58 +++++---- 3 files changed, 152 insertions(+), 22 deletions(-) create mode 100644 src/components/common/Tutorial/tutorial.tsx diff --git a/src/components/common/SearchResultsTable/searchResultsTable.tsx b/src/components/common/SearchResultsTable/searchResultsTable.tsx index 27a01a61..7f11329b 100644 --- a/src/components/common/SearchResultsTable/searchResultsTable.tsx +++ b/src/components/common/SearchResultsTable/searchResultsTable.tsx @@ -427,7 +427,7 @@ const SearchResultsTable = ({ Compare - + - + void; + close: () => void; + buttonText: string; +}; + +const TutorialPopup = ({ + anchorEl, + open, + children, + incrementStep, + close, + buttonText, +}: TutorialPopupProps) => { + return ( + +
+ + + + {children} + +
+
+ ); +}; + +const tutorialText: { [key: string]: ReactJSXElement } = { + 'step-1': ( + <> + Hi + Bye + + ), + 'step-2': ( + <> + Hello + Goodbye + + ), +}; + +type TutorialProps = { + open: boolean; + close: () => void; +}; + +const Tutorial = ({ open, close }: TutorialProps) => { + type Step = { + id: string; + element: Element; + tutorial: ReactJSXElement; + }; + 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]'); + const newSteps: Step[] = []; + elements.forEach((el) => { + const id = el.getAttribute('data-tutorial-id') as string; + newSteps.push({ + id: id, + element: el, + tutorial: tutorialText[id], + }); + }); + newSteps.sort((a, b) => Number(a.id) - Number(b.id)); + setSteps(newSteps); + setPlace(0); + }, [open]); + + return ( + <> + {steps.map(({ element, tutorial }, index) => ( + { + if (place === steps.length - 1) { + close(); + return; + } + setPlace(place + 1); + }} + close={close} + buttonText={index === steps.length - 1 ? 'Done' : 'Next'} + > + {tutorial} + + ))} + + ); +}; + +export default Tutorial; diff --git a/src/components/navigation/topMenu/topMenu.tsx b/src/components/navigation/topMenu/topMenu.tsx index 0890212f..dc5abc9a 100644 --- a/src/components/navigation/topMenu/topMenu.tsx +++ b/src/components/navigation/topMenu/topMenu.tsx @@ -1,4 +1,5 @@ -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'; @@ -7,6 +8,7 @@ import React, { useState } from 'react'; import Background from '../../../../public/background.png'; import SearchBar from '../../common/SearchBar/searchBar'; +import Tutorial from '../../common/Tutorial/tutorial'; /** * This is a component to hold UTD Trends branding and basic navigation @@ -51,6 +53,8 @@ export function TopMenu() { /> ); + const [openTutorial, setOpenTutorial] = useState(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!" /> + setOpenTutorial(false)} /> ); } From ed1c9cd6c8c64e1c12540b3d6bac596c9a2360ea Mon Sep 17 00:00:00 2001 From: Tyler Hill Date: Fri, 1 Nov 2024 14:03:22 -0500 Subject: [PATCH 2/8] Name change --- src/components/common/Tutorial/tutorial.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/common/Tutorial/tutorial.tsx b/src/components/common/Tutorial/tutorial.tsx index c43df88b..f4be7246 100644 --- a/src/components/common/Tutorial/tutorial.tsx +++ b/src/components/common/Tutorial/tutorial.tsx @@ -13,7 +13,7 @@ type TutorialPopupProps = { }; const TutorialPopup = ({ - anchorEl, + element, open, children, incrementStep, @@ -21,9 +21,10 @@ const TutorialPopup = ({ buttonText, }: TutorialPopupProps) => { return ( + ///TODO: scroll into view on open { {steps.map(({ element, tutorial }, index) => ( { if (place === steps.length - 1) { From 869fda4128cffbd005abc2f9853eeec8cb7c152e Mon Sep 17 00:00:00 2001 From: Tyler Hill Date: Fri, 1 Nov 2024 15:26:10 -0500 Subject: [PATCH 3/8] Styling --- src/components/common/Tutorial/tutorial.tsx | 91 +++++++++++++++------ src/pages/_app.tsx | 1 + src/styles/globals.css | 4 + 3 files changed, 69 insertions(+), 27 deletions(-) diff --git a/src/components/common/Tutorial/tutorial.tsx b/src/components/common/Tutorial/tutorial.tsx index f4be7246..2784e002 100644 --- a/src/components/common/Tutorial/tutorial.tsx +++ b/src/components/common/Tutorial/tutorial.tsx @@ -1,58 +1,93 @@ import { ReactJSXElement } from '@emotion/react/types/jsx-namespace'; import CloseIcon from '@mui/icons-material/Close'; -import { Button, IconButton, Popover, Typography } from '@mui/material'; +import { Backdrop, Button, IconButton, Popover } from '@mui/material'; import React, { useEffect, useState } from 'react'; type TutorialPopupProps = { - anchorEl: Element; + element: Element; open: boolean; - children: ReactJSXElement; incrementStep: () => void; close: () => void; + title: string; buttonText: string; + children: ReactJSXElement; }; const TutorialPopup = ({ element, open, - children, incrementStep, close, + title, buttonText, + children, }: TutorialPopupProps) => { + useEffect(() => { + if (open) { + element.classList.add('tutorial-raise'); + } else { + element.classList.remove('tutorial-raise'); + } + return () => element.classList.remove('tutorial-raise'); + }, [open]); + return ( - ///TODO: scroll into view on open -
- - - - {children} - +
+
+

{title}

+ + + +
+
{children}
+
); }; -const tutorialText: { [key: string]: ReactJSXElement } = { - 'step-1': ( - <> - Hi - Bye - - ), - 'step-2': ( - <> - Hello - Goodbye - - ), +type StepContent = { title: string; content: ReactJSXElement }; + +const tutorialText: { [key: string]: StepContent } = { + 'step-1': { + title: 'hey', + content: ( + <> +

Hi

+

Bye

+ + ), + }, + 'step-2': { + title: 'howdyyyyyyyyy', + content: ( + <> +

Hello

+

Goodbye

+

Hello

+

Goodbye

+ + ), + }, }; type TutorialProps = { @@ -64,7 +99,7 @@ const Tutorial = ({ open, close }: TutorialProps) => { type Step = { id: string; element: Element; - tutorial: ReactJSXElement; + tutorial: StepContent; }; const [steps, setSteps] = useState([]); const [place, setPlace] = useState(0); @@ -88,7 +123,8 @@ const Tutorial = ({ open, close }: TutorialProps) => { return ( <> - {steps.map(({ element, tutorial }, index) => ( + ({ zIndex: theme.zIndex.modal })} open={open} /> + {steps.map(({ element, tutorial: { title, content } }, index) => ( { setPlace(place + 1); }} close={close} + title={title} buttonText={index === steps.length - 1 ? 'Done' : 'Next'} > - {tutorial} + {content} ))} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index d0ddd218..0b95dbb3 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -80,6 +80,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/styles/globals.css b/src/styles/globals.css index 265750b2..5cbd67c5 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -27,4 +27,8 @@ body, .bg-darken { background-color: rgba(0, 0, 0, 0.6); } + + .tutorial-raise { + z-index: calc(var(--mui-zIndex-modal) + 1); + } } From 251ecbb3793275b1b876a51f8d637893537f3149 Mon Sep 17 00:00:00 2001 From: Tyler Hill Date: Sun, 3 Nov 2024 14:27:58 -0600 Subject: [PATCH 4/8] Scrolling into view --- .../common/SearchResultsTable/searchResultsTable.tsx | 6 +++--- src/components/common/Tutorial/tutorial.tsx | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/common/SearchResultsTable/searchResultsTable.tsx b/src/components/common/SearchResultsTable/searchResultsTable.tsx index 7f11329b..0f9fafbd 100644 --- a/src/components/common/SearchResultsTable/searchResultsTable.tsx +++ b/src/components/common/SearchResultsTable/searchResultsTable.tsx @@ -427,7 +427,7 @@ const SearchResultsTable = ({ Compare - + - + - + { if (open) { element.classList.add('tutorial-raise'); + element.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } else { element.classList.remove('tutorial-raise'); } From e5ab4c77cef2bb6a4efb60869d7bb5b782036a59 Mon Sep 17 00:00:00 2001 From: Tyler Hill Date: Sun, 3 Nov 2024 14:45:52 -0600 Subject: [PATCH 5/8] Account for not found elements --- src/components/common/Tutorial/tutorial.tsx | 46 +++++++++++---------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/components/common/Tutorial/tutorial.tsx b/src/components/common/Tutorial/tutorial.tsx index 82671b80..d6bc2b7c 100644 --- a/src/components/common/Tutorial/tutorial.tsx +++ b/src/components/common/Tutorial/tutorial.tsx @@ -66,10 +66,17 @@ const TutorialPopup = ({ ); }; -type StepContent = { title: string; content: ReactJSXElement }; +type StepTemplate = { + id: string; + element?: Element; + title: string; + content: ReactJSXElement; +}; +type Step = StepTemplate & { element: Element }; -const tutorialText: { [key: string]: StepContent } = { - 'step-1': { +const stepsTemplate: StepTemplate[] = [ + { + id: 'grades', title: 'hey', content: ( <> @@ -78,7 +85,8 @@ const tutorialText: { [key: string]: StepContent } = { ), }, - 'step-2': { + { + id: 'rating', title: 'howdyyyyyyyyy', content: ( <> @@ -89,7 +97,7 @@ const tutorialText: { [key: string]: StepContent } = { ), }, -}; +]; type TutorialProps = { open: boolean; @@ -97,35 +105,31 @@ type TutorialProps = { }; const Tutorial = ({ open, close }: TutorialProps) => { - type Step = { - id: string; - element: Element; - tutorial: StepContent; - }; 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]'); - const newSteps: Step[] = []; - elements.forEach((el) => { - const id = el.getAttribute('data-tutorial-id') as string; - newSteps.push({ - id: id, - element: el, - tutorial: tutorialText[id], - }); + if (!elements.length) { + close(); + return; + } + const newSteps = stepsTemplate; + elements.forEach((element) => { + const id = element.getAttribute('data-tutorial-id') as string; + newSteps[newSteps.findIndex((step) => step.id === id)].element = element; }); - newSteps.sort((a, b) => Number(a.id) - Number(b.id)); - setSteps(newSteps); + setSteps( + newSteps.filter((step) => typeof step.element !== 'undefined') as Step[], + ); setPlace(0); }, [open]); return ( <> ({ zIndex: theme.zIndex.modal })} open={open} /> - {steps.map(({ element, tutorial: { title, content } }, index) => ( + {steps.map(({ element, title, content }, index) => ( Date: Fri, 15 Nov 2024 18:32:57 -0600 Subject: [PATCH 6/8] how about them tutorial apples --- src/components/common/SearchBar/searchBar.tsx | 5 +- .../SearchResultsTable/searchResultsTable.tsx | 22 +++- src/components/common/Tutorial/tutorial.tsx | 120 +++++++++++++----- src/components/navigation/topMenu/topMenu.tsx | 5 +- src/pages/dashboard/index.tsx | 5 +- src/styles/globals.css | 5 + 6 files changed, 119 insertions(+), 43 deletions(-) diff --git a/src/components/common/SearchBar/searchBar.tsx b/src/components/common/SearchBar/searchBar.tsx index 5f6dc9ba..0669e6e8 100644 --- a/src/components/common/SearchBar/searchBar.tsx +++ b/src/components/common/SearchBar/searchBar.tsx @@ -210,7 +210,10 @@ const SearchBar = ({ }, []); return ( -
+
void; removeFromCompare: (arg0: SearchQuery) => void; color?: string; + showTutorial: boolean; }; function Row({ @@ -98,6 +99,7 @@ function Row({ addToCompare, removeFromCompare, color, + showTutorial, }: RowProps) { const [open, setOpen] = useState(false); @@ -122,8 +124,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 }; @@ -265,6 +275,7 @@ type SearchResultsTableProps = { const SearchResultsTable = ({ resultsLoading, + numSearches, includedResults, grades, rmp, @@ -457,7 +468,7 @@ const SearchResultsTable = ({ Name - + - + {resultsLoading === 'done' - ? sortedResults.map((result) => ( + ? sortedResults.map((result, index) => ( )) : Array(10) diff --git a/src/components/common/Tutorial/tutorial.tsx b/src/components/common/Tutorial/tutorial.tsx index d6bc2b7c..9f5efa05 100644 --- a/src/components/common/Tutorial/tutorial.tsx +++ b/src/components/common/Tutorial/tutorial.tsx @@ -10,7 +10,15 @@ type TutorialPopupProps = { close: () => void; title: string; buttonText: string; - children: ReactJSXElement; + anchorOrigin: { + vertical: 'center' | 'bottom' | 'top'; + horizontal: 'center' | 'left' | 'right'; + }; + transformOrigin: { + vertical: 'center' | 'bottom' | 'top'; + horizontal: 'center' | 'left' | 'right'; + }; + children: ReactJSXElement | string; }; const TutorialPopup = ({ @@ -20,30 +28,43 @@ const TutorialPopup = ({ close, title, buttonText, + anchorOrigin, + transformOrigin, children, }: TutorialPopupProps) => { useEffect(() => { if (open) { element.classList.add('tutorial-raise'); - element.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + 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'); - }, [open]); + return () => { + element.classList.remove('tutorial-raise'); + element.classList.remove('tutorial-table'); + }; + }, [open, element]); return (
@@ -70,32 +91,63 @@ type StepTemplate = { id: string; element?: Element; title: string; - content: ReactJSXElement; + 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: 'grades', - title: 'hey', - content: ( - <> -

Hi

-

Bye

- - ), + 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: 'rating', - title: 'howdyyyyyyyyy', - content: ( - <> -

Hello

-

Goodbye

-

Hello

-

Goodbye

- - ), + 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' }, }, ]; @@ -115,24 +167,26 @@ const Tutorial = ({ open, close }: TutorialProps) => { close(); return; } - const newSteps = stepsTemplate; + const newSteps = [...stepsTemplate]; elements.forEach((element) => { const id = element.getAttribute('data-tutorial-id') as string; - newSteps[newSteps.findIndex((step) => step.id === id)].element = element; + 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]); + }, [open, close]); return ( <> ({ zIndex: theme.zIndex.modal })} open={open} /> - {steps.map(({ element, title, content }, index) => ( + {steps.map(({ content, ...otherProps }, index) => ( { if (place === steps.length - 1) { @@ -142,8 +196,8 @@ const Tutorial = ({ open, close }: TutorialProps) => { setPlace(place + 1); }} close={close} - title={title} buttonText={index === steps.length - 1 ? 'Done' : 'Next'} + {...otherProps} > {content} diff --git a/src/components/navigation/topMenu/topMenu.tsx b/src/components/navigation/topMenu/topMenu.tsx index dc5abc9a..2f0ed5f0 100644 --- a/src/components/navigation/topMenu/topMenu.tsx +++ b/src/components/navigation/topMenu/topMenu.tsx @@ -4,7 +4,7 @@ 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 SearchBar from '../../common/SearchBar/searchBar'; @@ -54,6 +54,7 @@ export function TopMenu() { ); const [openTutorial, setOpenTutorial] = useState(false); + const closeTutorial = useCallback(() => setOpenTutorial(false), []); return ( <> @@ -116,7 +117,7 @@ export function TopMenu() { onClose={() => setOpenCopied(false)} message="Copied!" /> - setOpenTutorial(false)} /> + ); } diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx index 936467c5..7abc55db 100644 --- a/src/pages/dashboard/index.tsx +++ b/src/pages/dashboard/index.tsx @@ -772,9 +772,10 @@ export const Dashboard: NextPage = () => { - + { colorMap={colorMap} /> - +
diff --git a/src/styles/globals.css b/src/styles/globals.css index 5cbd67c5..7d3a5fc0 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -29,6 +29,11 @@ body, } .tutorial-raise { + position: relative; z-index: calc(var(--mui-zIndex-modal) + 1); + scroll-margin: 10rem; + } + .tutorial-table { + background-color: var(--mui-palette-background-paper); } } From da0ba6e10603238cd92910bee3d7861437ec8741 Mon Sep 17 00:00:00 2001 From: Tyler Hill Date: Fri, 15 Nov 2024 18:35:13 -0600 Subject: [PATCH 7/8] Move tutorial --- src/components/{common => dashboard}/Tutorial/tutorial.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/components/{common => dashboard}/Tutorial/tutorial.tsx (100%) diff --git a/src/components/common/Tutorial/tutorial.tsx b/src/components/dashboard/Tutorial/tutorial.tsx similarity index 100% rename from src/components/common/Tutorial/tutorial.tsx rename to src/components/dashboard/Tutorial/tutorial.tsx From 7f44b48041ed334e412c6ca2374bda3c40558796 Mon Sep 17 00:00:00 2001 From: Tyler Hill Date: Fri, 15 Nov 2024 18:44:18 -0600 Subject: [PATCH 8/8] Build fixes --- src/components/dashboard/Tutorial/tutorial.tsx | 2 +- src/components/navigation/topMenu/topMenu.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/dashboard/Tutorial/tutorial.tsx b/src/components/dashboard/Tutorial/tutorial.tsx index 9f5efa05..e53a9649 100644 --- a/src/components/dashboard/Tutorial/tutorial.tsx +++ b/src/components/dashboard/Tutorial/tutorial.tsx @@ -1,4 +1,4 @@ -import { ReactJSXElement } from '@emotion/react/types/jsx-namespace'; +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'; diff --git a/src/components/navigation/topMenu/topMenu.tsx b/src/components/navigation/topMenu/topMenu.tsx index b4bc61d0..949cef84 100644 --- a/src/components/navigation/topMenu/topMenu.tsx +++ b/src/components/navigation/topMenu/topMenu.tsx @@ -7,7 +7,7 @@ import { useRouter } from 'next/router'; import React, { useCallback, useState } from 'react'; import Background from '@/../public/background.png'; -import Tutorial from '@/components/common/Tutorial/tutorial'; +import Tutorial from '@/components/dashboard/Tutorial/tutorial'; import SearchBar from '@/components/search/SearchBar/searchBar'; /**