From 1930012afc24ef5be53b73aa732fb0829f5cd291 Mon Sep 17 00:00:00 2001 From: coderatomy <141822315+coderatomy@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:20:05 +0300 Subject: [PATCH 1/2] use route hooks from the pagewrapper (#1120) Signed-off-by: Ndibe Raymond Olisaemeka Co-authored-by: Ndibe Raymond Olisaemeka --- zubhub_frontend/zubhub/package.json | 2 + zubhub_frontend/zubhub/src/App.js | 199 ++++++++-------- .../zubhub/src/views/PageWrapper.jsx | 212 ++++-------------- 3 files changed, 140 insertions(+), 273 deletions(-) diff --git a/zubhub_frontend/zubhub/package.json b/zubhub_frontend/zubhub/package.json index e3337fd0..84d76c9c 100644 --- a/zubhub_frontend/zubhub/package.json +++ b/zubhub_frontend/zubhub/package.json @@ -18,6 +18,7 @@ "axios": "^1.5.1", "caniuse-lite": "^1.0.30001543", "classnames": "^2.2.6", + "clsx": "^2.1.0", "compressorjs": "^1.0.7", "dayjs": "^1.11.10", "formik": "^2.2.5", @@ -30,6 +31,7 @@ "lodash": "^4.17.21", "nanoid": "^3.3.1", "pdfmake": "^0.2.7", + "prop-types": "^15.0.0", "quill": "^1.3.7", "react": "^17.0.0", "react-confetti": "^6.1.0", diff --git a/zubhub_frontend/zubhub/src/App.js b/zubhub_frontend/zubhub/src/App.js index 41b77775..05d7bdef 100644 --- a/zubhub_frontend/zubhub/src/App.js +++ b/zubhub_frontend/zubhub/src/App.js @@ -1,6 +1,6 @@ import React, { useContext, useEffect } from 'react'; import { withTranslation } from 'react-i18next'; -import { Routes, Route, useLocation, useNavigate, useParams } from 'react-router-dom'; +import { Routes, Route } from 'react-router-dom'; import { connect } from 'react-redux'; import CreateActivity from './views/create_activity/CreateActivity'; import LoadingPage from './views/loading/LoadingPage'; @@ -69,15 +69,6 @@ const ThemeContext = React.createContext(); function App(props) { const theme = useContext(ThemeContext); - const location = useLocation(); - const navigate = useNavigate(); - const params = useParams(); - const routeProps = { - location, - params, - navigate, - }; - const handleThemeChange = async () => { try { const data = await API.theme(); @@ -99,8 +90,8 @@ function App(props) { - + + } /> @@ -108,8 +99,8 @@ function App(props) { - + + } /> @@ -117,8 +108,8 @@ function App(props) { - + + } /> @@ -126,8 +117,8 @@ function App(props) { - + + } /> @@ -135,8 +126,8 @@ function App(props) { - + + } /> @@ -144,8 +135,8 @@ function App(props) { - + + } /> @@ -153,8 +144,8 @@ function App(props) { - + + } /> @@ -162,8 +153,8 @@ function App(props) { - + + } /> @@ -171,8 +162,8 @@ function App(props) { - + + } /> @@ -180,8 +171,8 @@ function App(props) { - + + } /> @@ -189,8 +180,8 @@ function App(props) { - + + } /> @@ -198,8 +189,8 @@ function App(props) { - + + } /> @@ -207,8 +198,8 @@ function App(props) { - + + } /> @@ -216,8 +207,8 @@ function App(props) { - + + } /> @@ -225,8 +216,8 @@ function App(props) { - + + } /> @@ -234,16 +225,16 @@ function App(props) { - + + } /> - + + } /> @@ -251,8 +242,8 @@ function App(props) { - + + } /> @@ -260,8 +251,8 @@ function App(props) { - + + } /> @@ -269,8 +260,8 @@ function App(props) { - + + } /> @@ -278,8 +269,8 @@ function App(props) { - + + } /> @@ -287,8 +278,8 @@ function App(props) { - + + } /> @@ -296,8 +287,8 @@ function App(props) { - + + } /> @@ -305,8 +296,8 @@ function App(props) { - + + } /> @@ -314,8 +305,8 @@ function App(props) { - + + } /> @@ -323,8 +314,8 @@ function App(props) { - + + } /> @@ -332,8 +323,8 @@ function App(props) { - + + } /> @@ -341,8 +332,8 @@ function App(props) { - + + } /> @@ -350,8 +341,8 @@ function App(props) { - + + } /> @@ -359,8 +350,8 @@ function App(props) { - + + } /> @@ -368,8 +359,8 @@ function App(props) { - + + } /> @@ -377,8 +368,8 @@ function App(props) { - + + } /> @@ -386,24 +377,24 @@ function App(props) { - + + } /> - + + } /> - + + } /> @@ -411,8 +402,8 @@ function App(props) { - + + } /> @@ -420,8 +411,8 @@ function App(props) { - + + } /> @@ -429,8 +420,8 @@ function App(props) { - + + } /> @@ -438,8 +429,8 @@ function App(props) { - + + } /> @@ -447,8 +438,8 @@ function App(props) { - + + } /> @@ -456,8 +447,8 @@ function App(props) { - + + } /> @@ -465,8 +456,8 @@ function App(props) { - + + } /> @@ -474,8 +465,8 @@ function App(props) { - + + } /> @@ -483,8 +474,8 @@ function App(props) { - + + } /> @@ -492,8 +483,8 @@ function App(props) { - + + } /> @@ -501,8 +492,8 @@ function App(props) { - + + } /> @@ -510,8 +501,8 @@ function App(props) { - + + } /> diff --git a/zubhub_frontend/zubhub/src/views/PageWrapper.jsx b/zubhub_frontend/zubhub/src/views/PageWrapper.jsx index 13b78a99..7c5988b9 100644 --- a/zubhub_frontend/zubhub/src/views/PageWrapper.jsx +++ b/zubhub_frontend/zubhub/src/views/PageWrapper.jsx @@ -1,9 +1,9 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { Link, useNavigate } from 'react-router-dom'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { Link, useNavigate, useParams, useLocation } from 'react-router-dom'; import PropTypes from 'prop-types'; import clsx from 'clsx'; -import { connect, useSelector } from 'react-redux'; +import { connect } from 'react-redux'; import { ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; @@ -11,65 +11,30 @@ import 'react-toastify/dist/ReactToastify.css'; import { makeStyles } from '@mui/styles'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import TranslateIcon from '@mui/icons-material/Translate'; -import MenuRoundedIcon from '@mui/icons-material/MenuRounded'; -import SearchIcon from '@mui/icons-material/Search'; import { CssBaseline, Container, - AppBar, Toolbar, - ClickAwayListener, Typography, useScrollTrigger, Box, - IconButton, - OutlinedInput, - InputAdornment, - FormControl, - InputLabel, Fab, Zoom, - Menu, MenuItem, - Avatar, Select, - FormGroup, - InputBase, - TextField, - Tooltip, } from '@mui/material'; -import { - logout, - fetchHero, - handleScrollTopClick, - handleProfileMenuOpen, - handleProfileMenuClose, - handleChangeLanguage, - handleToggleSearchForm, - closeSearchFormOrIgnore, -} from './pageWrapperScripts'; - -import { getQueryParams, SearchType } from './search_results/searchResultsScripts'; - -import CustomButton from '../components/button/Button.js'; +import { fetchHero, handleScrollTopClick, handleProfileMenuClose, handleChangeLanguage } from './pageWrapperScripts'; + import LoadingPage from './loading/LoadingPage'; import * as AuthActions from '../store/actions/authActions'; import * as ProjectActions from '../store/actions/projectActions'; import unstructuredLogo from '../assets/images/logos/unstructured-logo.png'; -import logo from '../assets/images/logos/logo.png'; import styles from '../assets/js/styles/views/page_wrapper/pageWrapperStyles'; import commonStyles from '../assets/js/styles'; import languageMap from '../assets/js/languageMap.json'; -import InputSelect from '../components/input_select/InputSelect'; -import Autocomplete from '../components/autocomplete/Autocomplete'; -import API from '../api'; -import { throttle } from '../utils.js/index.js'; -import Option from '../components/autocomplete/Option'; -import NotificationButton from '../components/notification_button/NotificationButton'; -import BreadCrumb from '../components/breadCrumb/breadCrumb'; import DashboardLayout from '../layouts/DashboardLayout/DashboardLayout'; import Navbar from '../components/Navbar/Navbar'; import NotFoundPage from './not_found/NotFound'; @@ -91,19 +56,25 @@ function PageWrapper(props) { const classes = useStyles(); const common_classes = useCommonStyles(); const trigger = useScrollTrigger(); - const [searchType, setSearchType] = useState(getQueryParams(window.location.href).get('type') || SearchType.PROJECTS); - const formRef = useRef(); - const token = useSelector(state => state.auth.token); + const params = useParams(); + const location = useLocation(); + const routeProps = { + location, + params, + navigate, + }; const [state, setState] = React.useState({ - username: null, - anchor_el: null, loading: false, - open_search_form: false, }); - const [options, setOptions] = useState([]); - const [query, setQuery] = useState(props.location.search ? getQueryParams(window.location.href).get('q') : ''); + const handleSetState = obj => { + if (obj) { + Promise.resolve(obj).then(obj => { + setState(state => ({ ...state, ...obj })); + }); + } + }; const handleScroll = useCallback(() => { const currentScrollPos = window.pageYOffset; @@ -111,8 +82,6 @@ function PageWrapper(props) { setPrevScrollPos(currentScrollPos); }, [prevScrollPos]); - - useEffect(() => { window.addEventListener('scroll', handleScroll); return () => { @@ -120,53 +89,6 @@ function PageWrapper(props) { }; }, [handleScroll]); - const throttledFetchOptions = useMemo( - () => - throttle(async (query, searchType) => { - if (query?.length === 0) { - setOptions([]); - return; - } - - const api = new API(); - let completions = []; - if (searchType === SearchType.TAGS) { - completions = await api.autocompleteTags({ query, token }); - completions = completions.map(({ name }) => ({ - title: name, - })); - } else if (searchType === SearchType.PROJECTS) { - completions = await api.autocompleteProjects({ query, token }); - completions = completions.map(({ id, title, creator, images }) => ({ - title, - shortInfo: creator.username, - image: images.length > 0 ? images[0].image_url : null, - link: `/projects/${id}`, - })); - } else { - completions = await api.autocompleteCreators({ query, token }); - completions = completions.map(({ username, avatar }) => ({ - title: username, - image: avatar, - link: `/creators/${username}`, - })); - } - setOptions(completions); - }, 2), - [], - ); - - useEffect(() => { - throttledFetchOptions( - query || (props.location.search && getQueryParams(window.location.href).get('q')), - searchType, - ); - }, [query, searchType]); - - useEffect(() => { - throttledFetchOptions.cancel(); - }, []); - useEffect(() => { handleSetState({ loading: true }); fetchHero(props) @@ -184,53 +106,15 @@ function PageWrapper(props) { handleSetState(handleProfileMenuClose()); }, [trigger]); - const handleSetState = obj => { - if (obj) { - Promise.resolve(obj).then(obj => { - setState(state => ({ ...state, ...obj })); - }); - } - }; - - const onSearchOptionClick = async (_, option) => { - if (!option || !option.title) return; - - await new Promise(resolve => setTimeout(resolve, 100)); - if (option.link) { - window.history.pushState({}, '', option.link); - window.location.reload(); - return; - } - - const queryParams = new URLSearchParams({ - type: searchType, - q: option.title, - }); - window.history.pushState({}, '', `/search?${queryParams}`); - window.location.reload(); - }; - - const handleSubmit = e => { - e.preventDefault(); - if (query.length == 0) return; - const queryParams = new URLSearchParams({ - type: searchType, - q: query, - }); - - window.history.pushState({}, '', `/search?${queryParams}`); - window.location.reload(); - }; - - const handleTextField = event => { - setQuery(event.target.value); - }; - - const { anchor_el, loading, open_search_form } = state; + const { loading } = state; const { t } = props; const { zubhub, hero } = props.projects; - const profileMenuOpen = Boolean(anchor_el); + // TODO: remove childrenRenderer and use children directly. this will likely mean having useNavigate, useParams, + // useLocation in every component that needs them. + // React.cloneElement makes our code brittle: see https://react.dev/reference/react/cloneElement + const childrenRenderer = () => + React.Children.map(props.children, child => React.cloneElement(child, { ...routeProps, ...props })); return ( <> @@ -240,7 +124,7 @@ function PageWrapper(props) { - {props.auth?.token ? {loading ? : props.children} : null} + {props.auth?.token ? {loading ? : childrenRenderer()} : null} {!props.auth?.token && ![ '/', @@ -255,8 +139,8 @@ function PageWrapper(props) { '/challenge', '/password-reset', '/email-confirm', - '/password-reset-confirm' - ].includes(props.location?.pathname) && ( + '/password-reset-confirm', + ].includes(location?.pathname) && (
@@ -276,8 +160,8 @@ function PageWrapper(props) { '/about', '/challenge', '/email-confirm', - '/password-reset-confirm' - ].includes(props.location?.pathname) &&
{props.children}
} + '/password-reset-confirm', + ].includes(location?.pathname) &&
{childrenRenderer()}
}