diff --git a/client/src/app/layout/ContentContainer/ContentContainer.tsx b/client/src/app/layout/ContentContainer/ContentContainer.tsx index ccdc02df..cfd8af67 100644 --- a/client/src/app/layout/ContentContainer/ContentContainer.tsx +++ b/client/src/app/layout/ContentContainer/ContentContainer.tsx @@ -1,8 +1,8 @@ import { PropsWithChildren } from 'react' -import { Container } from '@mantine/core' +import { Container, MantineSize } from '@mantine/core' interface IContentContainerProps { - size?: 'xl' | 'md' + size?: MantineSize } const ContentContainer = (props: PropsWithChildren) => { diff --git a/client/src/app/layout/PublicArea/PublicArea.tsx b/client/src/app/layout/PublicArea/PublicArea.tsx new file mode 100644 index 00000000..305b8681 --- /dev/null +++ b/client/src/app/layout/PublicArea/PublicArea.tsx @@ -0,0 +1,33 @@ +import React, { PropsWithChildren } from 'react' +import Footer from './components/Footer/Footer' +import { Link } from 'react-router-dom' +import { CaretLeft } from 'phosphor-react' +import { Anchor, MantineSize } from '@mantine/core' +import ContentContainer from '../ContentContainer/ContentContainer' +import ScrollToTop from '../ScrollToTop/ScrollToTop' + +interface IPublicAreaProps { + withBackButton?: boolean + size?: MantineSize +} + +const PublicArea = (props: PropsWithChildren) => { + const { withBackButton = false, size = 'md', children } = props + + return ( +
+ + {withBackButton && ( + + Back + + )} + {children} + +
+ +
+ ) +} + +export default PublicArea diff --git a/client/src/pages/LandingPage/components/Footer/Footer.module.css b/client/src/app/layout/PublicArea/components/Footer/Footer.module.css similarity index 100% rename from client/src/pages/LandingPage/components/Footer/Footer.module.css rename to client/src/app/layout/PublicArea/components/Footer/Footer.module.css diff --git a/client/src/pages/LandingPage/components/Footer/Footer.tsx b/client/src/app/layout/PublicArea/components/Footer/Footer.tsx similarity index 85% rename from client/src/pages/LandingPage/components/Footer/Footer.tsx rename to client/src/app/layout/PublicArea/components/Footer/Footer.tsx index 80374fc0..dceb146c 100644 --- a/client/src/pages/LandingPage/components/Footer/Footer.tsx +++ b/client/src/app/layout/PublicArea/components/Footer/Footer.tsx @@ -1,8 +1,8 @@ import * as classes from './Footer.module.css' import { Anchor, Container, Group } from '@mantine/core' -import { GLOBAL_CONFIG } from '../../../../config/global' +import { GLOBAL_CONFIG } from '../../../../../config/global' import { Link } from 'react-router-dom' -import ColorSchemeToggleButton from '../../../../components/ColorSchemeToggleButton/ColorSchemeToggleButton' +import ColorSchemeToggleButton from '../../../../../components/ColorSchemeToggleButton/ColorSchemeToggleButton' const links = [ { link: '/about', label: 'About', visible: true }, diff --git a/client/src/app/layout/ScrollToTop/ScrollToTop.tsx b/client/src/app/layout/ScrollToTop/ScrollToTop.tsx index 8b4a632d..4accb1fe 100644 --- a/client/src/app/layout/ScrollToTop/ScrollToTop.tsx +++ b/client/src/app/layout/ScrollToTop/ScrollToTop.tsx @@ -1,10 +1,8 @@ import React, { useEffect } from 'react' -import { useWindowScroll } from '@mantine/hooks' import { useLocation } from 'react-router-dom' import { useNavigationType } from 'react-router' const ScrollToTop = () => { - const [, scrollTo] = useWindowScroll() const navigationType = useNavigationType() const location = useLocation() @@ -13,7 +11,7 @@ const ScrollToTop = () => { return } - scrollTo({ y: 0 }) + window.scrollTo(0, 0) }, [location.pathname, navigationType]) return <> diff --git a/client/src/config/global.ts b/client/src/config/global.ts index 0a523966..5026ae26 100644 --- a/client/src/config/global.ts +++ b/client/src/config/global.ts @@ -15,9 +15,9 @@ const getEnvironmentVariable = (key: string, useJson = false): T | u } export const GLOBAL_CONFIG: IGlobalConfig = { - title: getEnvironmentVariable('APPLICATION_TITLE') || 'Thesis Track', + title: getEnvironmentVariable('APPLICATION_TITLE') || 'ThesisTrack', - chair_name: getEnvironmentVariable('CHAIR_NAME') || 'Thesis Track', + chair_name: getEnvironmentVariable('CHAIR_NAME') || 'ThesisTrack', chair_url: getEnvironmentVariable('CHAIR_URL') || window.origin, allow_suggested_topics: (getEnvironmentVariable('ALLOW_SUGGESTED_TOPICS') || 'true') === 'true', diff --git a/client/src/hooks/theme.ts b/client/src/hooks/theme.ts index 6fcc1043..71741e70 100644 --- a/client/src/hooks/theme.ts +++ b/client/src/hooks/theme.ts @@ -1,6 +1,7 @@ -import { useDocumentTitle, useMediaQuery } from '@mantine/hooks' +import { useMediaQuery } from '@mantine/hooks' import { useMantineColorScheme, useMantineTheme } from '@mantine/core' import { GLOBAL_CONFIG } from '../config/global' +import { useEffect } from 'react' export function useIsSmallerBreakpoint(breakpoint: string) { const theme = useMantineTheme() @@ -15,7 +16,15 @@ export function useIsBiggerThanBreakpoint(breakpoint: string) { } export function usePageTitle(title: string) { - useDocumentTitle(`${title} - ${GLOBAL_CONFIG.title}`) + useEffect(() => { + const previousTitle = document.title + + document.title = `${title} - ${GLOBAL_CONFIG.title}` + + return () => { + document.title = previousTitle + } + }, [title]) } export function useHighlightedBackgroundColor(selected: boolean) { diff --git a/client/src/pages/AboutPage/AboutPage.tsx b/client/src/pages/AboutPage/AboutPage.tsx index 0a7beb6e..5ac2c2bc 100644 --- a/client/src/pages/AboutPage/AboutPage.tsx +++ b/client/src/pages/AboutPage/AboutPage.tsx @@ -1,24 +1,22 @@ -import ContentContainer from '../../app/layout/ContentContainer/ContentContainer' import { Anchor, Center, Image, List, Stack, Text, Title } from '@mantine/core' -import { Link } from 'react-router-dom' import flowchart from './flowchart.svg' -import { CaretLeft } from 'phosphor-react' +import { usePageTitle } from '../../hooks/theme' +import PublicArea from '../../app/layout/PublicArea/PublicArea' const AboutPage = () => { + usePageTitle('About') + return ( - + - - Back - - Thesis Track + ThesisTrack - Thesis Track is an{' '} + ThesisTrack is an{' '} open source {' '} - web application that represents the complete thesis lifecycle of theses applied for and - supervised at the chair. + web application that integrates the complete thesis lifecycle for theses supervised at a + chair. It should help advisors and supervisors to save time on reoccurring processes. Contributors @@ -38,7 +36,7 @@ const AboutPage = () => { - + ) } diff --git a/client/src/pages/ImprintPage/ImprintPage.tsx b/client/src/pages/ImprintPage/ImprintPage.tsx index c93d8df0..837a7a0c 100644 --- a/client/src/pages/ImprintPage/ImprintPage.tsx +++ b/client/src/pages/ImprintPage/ImprintPage.tsx @@ -1,14 +1,17 @@ -import ContentContainer from '../../app/layout/ContentContainer/ContentContainer' import { Title } from '@mantine/core' import DocumentEditor from '../../components/DocumentEditor/DocumentEditor' import { GLOBAL_CONFIG } from '../../config/global' +import { usePageTitle } from '../../hooks/theme' +import PublicArea from '../../app/layout/PublicArea/PublicArea' const ImprintPage = () => { + usePageTitle('Imprint') + return ( - + Imprint - + ) } diff --git a/client/src/pages/LandingPage/LandingPage.tsx b/client/src/pages/LandingPage/LandingPage.tsx index 83d537b0..dad6e923 100644 --- a/client/src/pages/LandingPage/LandingPage.tsx +++ b/client/src/pages/LandingPage/LandingPage.tsx @@ -1,18 +1,20 @@ import HeroSection from './components/HeroSection/HeroSection' import TopicsProvider from '../../contexts/TopicsProvider/TopicsProvider' import TopicsTable from '../../components/TopicsTable/TopicsTable' -import ContentContainer from '../../app/layout/ContentContainer/ContentContainer' import { Button, Group, Space, Title } from '@mantine/core' import React from 'react' import { Link } from 'react-router-dom' import PublishedTheses from './components/PublishedTheses/PublishedTheses' -import Footer from './components/Footer/Footer' +import { usePageTitle } from '../../hooks/theme' +import PublicArea from '../../app/layout/PublicArea/PublicArea' const LandingPage = () => { + usePageTitle('Find or Propose a Thesis Topic') + return (
- + Open Topics @@ -50,8 +52,7 @@ const LandingPage = () => { </TopicsProvider> <Space my='md' /> <PublishedTheses /> - </ContentContainer> - <Footer /> + </PublicArea> </div> ) } diff --git a/client/src/pages/LandingPage/components/HeroSection/HeroSection.tsx b/client/src/pages/LandingPage/components/HeroSection/HeroSection.tsx index 4c9d77ed..209c689b 100644 --- a/client/src/pages/LandingPage/components/HeroSection/HeroSection.tsx +++ b/client/src/pages/LandingPage/components/HeroSection/HeroSection.tsx @@ -21,7 +21,7 @@ export function HeroSection() { <Container p={0} size={700}> <Text size='lg' c='dimmed' className={classes.description}> - Whether you're looking for inspiration or have a unique idea in mind, Thesis Track + Whether you're looking for inspiration or have a unique idea in mind, ThesisTrack makes it easy. Explore topics posted by instructors or suggest your own. </Text> </Container> diff --git a/client/src/pages/PresentationPage/PresentationPage.tsx b/client/src/pages/PresentationPage/PresentationPage.tsx index 4ba140cd..33c1208b 100644 --- a/client/src/pages/PresentationPage/PresentationPage.tsx +++ b/client/src/pages/PresentationPage/PresentationPage.tsx @@ -12,12 +12,15 @@ import { Anchor, Divider, Grid, Stack, Title } from '@mantine/core' import LabeledItem from '../../components/LabeledItem/LabeledItem' import { formatDate, formatPresentationType } from '../../utils/format' import { GLOBAL_CONFIG } from '../../config/global' +import { usePageTitle } from '../../hooks/theme' const PresentationPage = () => { const { presentationId } = useParams<{ presentationId: string }>() const [presentation, setPresentation] = useState<IPublishedPresentation | false>() + usePageTitle(presentation ? presentation.thesis.title : 'Presentation') + useEffect(() => { setPresentation(undefined) diff --git a/client/src/pages/PrivacyPage/PrivacyPage.tsx b/client/src/pages/PrivacyPage/PrivacyPage.tsx index f774fe97..2f307803 100644 --- a/client/src/pages/PrivacyPage/PrivacyPage.tsx +++ b/client/src/pages/PrivacyPage/PrivacyPage.tsx @@ -1,14 +1,17 @@ -import ContentContainer from '../../app/layout/ContentContainer/ContentContainer' import { Title } from '@mantine/core' import { GLOBAL_CONFIG } from '../../config/global' import DocumentEditor from '../../components/DocumentEditor/DocumentEditor' +import { usePageTitle } from '../../hooks/theme' +import PublicArea from '../../app/layout/PublicArea/PublicArea' const PrivacyPage = () => { + usePageTitle('Privacy') + return ( - <ContentContainer size='md'> + <PublicArea withBackButton={true}> <Title mb='md'>Privacy - + ) } diff --git a/client/src/pages/ReplaceApplicationPage/ReplaceApplicationPage.tsx b/client/src/pages/ReplaceApplicationPage/ReplaceApplicationPage.tsx index 2f5e80a4..2424defd 100644 --- a/client/src/pages/ReplaceApplicationPage/ReplaceApplicationPage.tsx +++ b/client/src/pages/ReplaceApplicationPage/ReplaceApplicationPage.tsx @@ -7,13 +7,15 @@ import SelectTopicStep from './components/SelectTopicStep/SelectTopicStep' import StudentInformationStep from './components/StudentInformationStep/StudentInformationStep' import MotivationStep from './components/MotivationStep/MotivationStep' import TopicsProvider from '../../contexts/TopicsProvider/TopicsProvider' -import { useWindowScroll } from '@mantine/hooks' import { IApplication } from '../../requests/responses/application' import { doRequest } from '../../requests/request' +import { usePageTitle } from '../../hooks/theme' const ReplaceApplicationPage = () => { const { topicId, applicationId } = useParams<{ topicId: string; applicationId: string }>() + usePageTitle('Submit Application') + const [application, setApplication] = useState() useEffect(() => { @@ -35,7 +37,6 @@ const ReplaceApplicationPage = () => { } }, [applicationId]) - const [, scrollTo] = useWindowScroll() const navigate = useNavigate() const topic = useTopic(topicId) @@ -50,7 +51,7 @@ const ReplaceApplicationPage = () => { navigate(`/submit-application`, { replace: true }) } - scrollTo({ y: 0 }) + window.scrollTo(0, 0) setStep(value) } diff --git a/client/src/pages/ReviewApplicationPage/ReviewApplicationPage.tsx b/client/src/pages/ReviewApplicationPage/ReviewApplicationPage.tsx index 43ca2370..631e8b9f 100644 --- a/client/src/pages/ReviewApplicationPage/ReviewApplicationPage.tsx +++ b/client/src/pages/ReviewApplicationPage/ReviewApplicationPage.tsx @@ -6,13 +6,15 @@ import ApplicationsProvider from '../../contexts/ApplicationsProvider/Applicatio import { Grid, Text, Center } from '@mantine/core' import ApplicationsSidebar from './components/ApplicationsSidebar/ApplicationsSidebar' import ApplicationReviewBody from './components/ApplicationReviewBody/ApplicationReviewBody' -import { useIsSmallerBreakpoint } from '../../hooks/theme' +import { useIsSmallerBreakpoint, usePageTitle } from '../../hooks/theme' import ApplicationModal from '../../components/ApplicationModal/ApplicationModal' const ReviewApplicationPage = () => { const navigate = useNavigate() const { applicationId } = useParams<{ applicationId: string }>() + usePageTitle('Review Applications') + const isSmallScreen = useIsSmallerBreakpoint('md') const [application, setApplication] = useState() @@ -58,6 +60,7 @@ const ReviewApplicationPage = () => { { navigate(`/applications/${newApplication.applicationId}`, { replace: true, diff --git a/client/src/pages/ReviewApplicationPage/components/ApplicationReviewBody/ApplicationReviewBody.tsx b/client/src/pages/ReviewApplicationPage/components/ApplicationReviewBody/ApplicationReviewBody.tsx index 86a2af6d..663310a4 100644 --- a/client/src/pages/ReviewApplicationPage/components/ApplicationReviewBody/ApplicationReviewBody.tsx +++ b/client/src/pages/ReviewApplicationPage/components/ApplicationReviewBody/ApplicationReviewBody.tsx @@ -3,7 +3,6 @@ import ApplicationReviewForm from '../../../../components/ApplicationReviewForm/ import { Divider, Stack } from '@mantine/core' import React, { useEffect } from 'react' import { IApplication } from '../../../../requests/responses/application' -import { useWindowScroll } from '@mantine/hooks' import ApplicationRejectButton from '../../../../components/ApplicationRejectButton/ApplicationRejectButton' interface IApplicationReviewBodyProps { @@ -14,10 +13,8 @@ interface IApplicationReviewBodyProps { const ApplicationReviewBody = (props: IApplicationReviewBodyProps) => { const { application, onChange } = props - const [, scrollTo] = useWindowScroll() - useEffect(() => { - scrollTo({ y: 0 }) + window.scrollTo(0, 0) }, [application.applicationId]) return ( diff --git a/client/src/pages/ReviewApplicationPage/components/ApplicationsSidebar/ApplicationsSidebar.tsx b/client/src/pages/ReviewApplicationPage/components/ApplicationsSidebar/ApplicationsSidebar.tsx index e7863a61..500e08fd 100644 --- a/client/src/pages/ReviewApplicationPage/components/ApplicationsSidebar/ApplicationsSidebar.tsx +++ b/client/src/pages/ReviewApplicationPage/components/ApplicationsSidebar/ApplicationsSidebar.tsx @@ -7,11 +7,12 @@ import ApplicationListItem from '../ApplicationListItem/ApplicationListItem' interface IApplicationsSidebarProps { selected: IApplication | undefined + isSmallScreen: boolean onSelect: (application: IApplication) => unknown } const ApplicationsSidebar = (props: IApplicationsSidebarProps) => { - const { selected, onSelect } = props + const { selected, isSmallScreen, onSelect } = props const { page, setPage, applications } = useApplicationsContext() @@ -54,6 +55,10 @@ const ApplicationsSidebar = (props: IApplicationsSidebarProps) => { }, [applications, page, selectedIndex]) useEffect(() => { + if (isSmallScreen) { + return + } + if (page === 0 && !startAtLastApplication) { return } @@ -65,7 +70,12 @@ const ApplicationsSidebar = (props: IApplicationsSidebarProps) => { : applications.content[0], ) } - }, [page, startAtLastApplication, applications?.content.map((x) => x.applicationId).join(',')]) + }, [ + page, + startAtLastApplication, + isSmallScreen, + applications?.content.map((x) => x.applicationId).join(','), + ]) return ( diff --git a/client/src/pages/TopicPage/TopicPage.tsx b/client/src/pages/TopicPage/TopicPage.tsx index cd6500e8..f0f2ab11 100644 --- a/client/src/pages/TopicPage/TopicPage.tsx +++ b/client/src/pages/TopicPage/TopicPage.tsx @@ -19,7 +19,7 @@ const TopicPage = () => { const topic = useTopic(topicId) - usePageTitle('Topic') + usePageTitle(topic ? topic.title : 'Topic') if (topic === false) { return