Skip to content

Commit

Permalink
[refactor] update navigation logic; replace home route with Navigatio…
Browse files Browse the repository at this point in the history
…nSelector
  • Loading branch information
cupoftea4 committed Dec 23, 2024
1 parent 2fd0223 commit c566d59
Show file tree
Hide file tree
Showing 13 changed files with 93 additions and 64 deletions.
4 changes: 2 additions & 2 deletions public/sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ async function networkFirst(request) {
const cacheRes = shouldCache(request, response);

if (cacheRes) {
cache.put(cacheRes === "home" ? "/" : request, response.clone());
cache.put(cacheRes === "home" ? "/home" : request, response.clone());
}

return response ?? await cache.match(request);
} catch (error) {
return await cache.match(request).then(async res => res ?? await cache.match("/"));
return await cache.match(request).then(async res => res ?? await cache.match("/home"));
}

}
4 changes: 3 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { BrowserRouter, Route, Routes } from "react-router-dom";
import { ToastContainer as MessageToast } from "react-toastify";
import HomePage from "./pages/HomePage";
import LoadingPage from "./pages/LoadingPage";
import NavigationSelector from "./pages/NavigationSelector";
import TimetablePage from "./pages/TimetablePage";
import { Status } from "./types/utils";
import { RECEIVED_DONATION_NOTIFICATION, TOAST_AUTO_CLOSE_TIME } from "./utils/constants";
Expand Down Expand Up @@ -43,7 +44,8 @@ const App = () => {
<>
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage timetableType="timetable" />} />
<Route path="/" element={<NavigationSelector />} />
<Route path="home" element={<HomePage timetableType="timetable" />} />
<Route path="selective" element={<HomePage timetableType="selective" />} />
<Route path="lecturer" element={<HomePage timetableType="lecturer" />} />
<Route path="/:group" element={<TimetablePage />} />
Expand Down
4 changes: 0 additions & 4 deletions src/context/datalistFocus.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import useInputFocus from "@/hooks/useFocus";
import useWindowDimensions from "@/hooks/useWindowDimensions";
import { TABLET_SCREEN_BREAKPOINT } from "@/utils/constants";
import type React from "react";
import { type ReactNode, createContext, useContext } from "react";

Expand All @@ -14,10 +12,8 @@ interface DatalistFocusContextType {
const DatalistFocusContext = createContext<DatalistFocusContextType | undefined>(undefined);

const DatalistFocusProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const { width } = useWindowDimensions();
const { ref, isFocused, focus, blur } = useInputFocus<HTMLDivElement>({
childInput: true,
initFocus: width > TABLET_SCREEN_BREAKPOINT,
});

return (
Expand Down
6 changes: 2 additions & 4 deletions src/features/header/HomeHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const HeaderPanel: FC<OwnProps> = ({ timetableType, className }) => {
() => width < TABLET_SCREEN_BREAKPOINT && width > NARROW_SCREEN_BREAKPOINT,
[width]
);
const { isFocused, blur, focus } = useDatalistFocus();
const { isFocused, blur } = useDatalistFocus();
const [showSearchBar, setShowSearchBar] = useState(!shouldShrinkSearchBar || isFocused);

useEffect(() => {
Expand All @@ -40,10 +40,8 @@ const HeaderPanel: FC<OwnProps> = ({ timetableType, className }) => {
useEffect(() => {
if (!showSearchBar) {
blur();
} else {
focus();
}
}, [showSearchBar, blur, focus]);
}, [showSearchBar, blur]);

const toggleSearchBar = (state = !showSearchBar) => {
if (shouldShrinkSearchBar) {
Expand Down
8 changes: 3 additions & 5 deletions src/features/header/TimetableHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import HomeIcon from "@/assets/HomeIcon";
import usePageTitle from "@/hooks/usePageTitle";
import useWindowDimensions from "@/hooks/useWindowDimensions";
import { useIsMobile } from "@/hooks/useWindowDimensions";
import Toggle from "@/shared/Toggle";
import { classes } from "@/styles/utils";
import type { HalfTerm } from "@/types/timetable";
import { MOBILE_SCREEN_BREAKPOINT } from "@/utils/constants";
import TimetableManager from "@/utils/data/TimetableManager";
import { isMerged } from "@/utils/timetable";
import Toast from "@/utils/toasts";
Expand Down Expand Up @@ -41,8 +40,7 @@ const TimetableHeader: FC<OwnProps> = ({
const [isSecondWeek, setIsSecondWeek] = weekState;
const navigate = useNavigate();
const group = useParams().group?.trim() ?? "";
const { width } = useWindowDimensions();
const isMobile = width < MOBILE_SCREEN_BREAKPOINT;
const isMobile = useIsMobile();
const groupTitle = timetableType === "merged" ? "Мій розклад" : group;
usePageTitle(groupTitle);

Expand Down Expand Up @@ -70,7 +68,7 @@ const TimetableHeader: FC<OwnProps> = ({
<div className={"flex gap-1"}>
<Link
state={{ force: true }}
to="/"
to="/home"
aria-label="Home"
type="button"
className={classes("icon-button", "transition duration-300")}
Expand Down
8 changes: 3 additions & 5 deletions src/features/header/components/Navigation.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import useWindowDimensions from "@/hooks/useWindowDimensions";
import { useIsMobile } from "@/hooks/useWindowDimensions";
import MobileSelect from "@/shared/MobileSelect";
import { classes } from "@/styles/utils";
import type { TimetableType } from "@/types/timetable";
import { TABLET_SCREEN_BREAKPOINT } from "@/utils/constants";
import type { FC } from "react";
import { Link, useNavigate } from "react-router-dom";
import styles from "./Navigation.module.scss";
Expand All @@ -18,8 +17,7 @@ type OwnProps = {
};

const Navigation: FC<OwnProps> = ({ timetableType }) => {
const { width } = useWindowDimensions();
const isMobile = width < TABLET_SCREEN_BREAKPOINT;
const isMobile = useIsMobile();
const navigate = useNavigate();

const onMobileSelectChange = (type: string) => {
Expand All @@ -32,7 +30,7 @@ const Navigation: FC<OwnProps> = ({ timetableType }) => {
navigationItems.map((type) => (
<Link
state={{ force: true }}
to={`/${type.value === "timetable" ? "" : type.value}`}
to={`/${type.value === "timetable" ? "home" : type.value}`}
key={type.value}
className={classes(styles["nav-link"], timetableType === type.value && styles.active)}
>
Expand Down
5 changes: 4 additions & 1 deletion src/features/home/TimetablesSelection.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useDatalistFocus } from "@/context/datalistFocus";
import { useIsMobile } from "@/hooks/useWindowDimensions";
import { classes } from "@/styles/utils";
import { sortGroupsByYear } from "@/utils/timetable";
import { type FC, useState } from "react";
Expand All @@ -20,6 +21,7 @@ type OwnProps = {
const TimetablesSelection: FC<OwnProps> = ({ timetables, withYears = false }) => {
const groupsByYear = sortGroupsByYear(timetables);
const [expandedYear, setExpandedYear] = useState<Year | null>(null); // for mobile onClick event and keyboard navigation
const isMobile = useIsMobile();
const { focus } = useDatalistFocus();

return (
Expand All @@ -30,9 +32,10 @@ const TimetablesSelection: FC<OwnProps> = ({ timetables, withYears = false }) =>
groupsByYear[year]?.length && groupsByYear[year]?.length !== 0 ? (
<ul
key={year}
className={classes(styles.year, expandedYear === year && styles.expanded)}
className={classes(styles.year, (expandedYear === year || isMobile) && styles.expanded)}
data-value={`${year} Курс`}
onClick={() => {
if (isMobile) return;
expandedYear === year ? setExpandedYear(null) : setExpandedYear(year);
}}
>
Expand Down
7 changes: 3 additions & 4 deletions src/features/timetable/Timetable.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import useWindowDimensions from "@/hooks/useWindowDimensions";
import { useIsMobile } from "@/hooks/useWindowDimensions";
import { classes } from "@/styles/utils";
import type { TimetableItem } from "@/types/timetable";
import { DEVELOP, TIMETABLE_SCREEN_BREAKPOINT } from "@/utils/constants";
import { DEVELOP } from "@/utils/constants";
import { getCurrentUADate, stringToDate } from "@/utils/date";
import { generateSaturdayLessons, lessonsTimes, skeletonTimetable, unique } from "@/utils/timetable";
import { type FC, useCallback, useEffect, useMemo, useState } from "react";
Expand All @@ -28,8 +28,7 @@ const Timetable: FC<OwnProps> = ({
hasCellSubgroups,
isLoading,
}) => {
const { width } = useWindowDimensions();
const isMobile = width < TIMETABLE_SCREEN_BREAKPOINT;
const isMobile = useIsMobile();

const timetable = useMemo(() => {
return [...originalTimetable, ...generateSaturdayLessons(originalTimetable)];
Expand Down
11 changes: 11 additions & 0 deletions src/hooks/useWindowDimensions.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { MOBILE_SCREEN_BREAKPOINT, TABLET_SCREEN_BREAKPOINT } from "@/utils/constants";
import { useEffect, useState } from "react";

function getWindowDimensions() {
Expand All @@ -24,3 +25,13 @@ export default function useWindowDimensions() {

return windowDimensions;
}

export function useIsMobile() {
const { width } = useWindowDimensions();
return width < MOBILE_SCREEN_BREAKPOINT;
}

export function useIsTablet() {
const { width } = useWindowDimensions();
return width < TABLET_SCREEN_BREAKPOINT;
}
80 changes: 44 additions & 36 deletions src/pages/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import catImage from "@/assets/cat.svg";
import { DatalistFocusProvider } from "@/context/datalistFocus";
import HeaderPanel from "@/features/header/HomeHeader";
import TimetablesSelection from "@/features/home/TimetablesSelection";
import useWindowDimensions from "@/hooks/useWindowDimensions";
import { useIsTablet } from "@/hooks/useWindowDimensions";
import List from "@/shared/List";
import { classes } from "@/styles/utils";
import type { TimetableType } from "@/types/timetable";
import { BUG_REPORT_LINK, DONATION_LINK, TABLET_SCREEN_BREAKPOINT } from "@/utils/constants";
import { BUG_REPORT_LINK, DONATION_LINK } from "@/utils/constants";
import TimetableManager from "@/utils/data/TimetableManager";
import Toast from "@/utils/toasts";
import { type FC, useCallback, useEffect, useState } from "react";
import { useLocation, useNavigate, useSearchParams } from "react-router-dom";
import { type FC, useCallback, useEffect, useRef, useState } from "react";
import { useSearchParams } from "react-router-dom";
import styles from "./HomePage.module.scss";

type OwnProps = {
Expand All @@ -26,44 +26,45 @@ const HomePage: FC<OwnProps> = ({ timetableType }) => {
const [selectedFirst, setSelectedFirst] = useState<string | null>(null);
const [selectedSecond, setSelectedSecond] = useState<string | null>(null);

const { state }: { state: { force: boolean } | null } = useLocation();
const { force } = state ?? {};

const { width } = useWindowDimensions();
const navigate = useNavigate();
const isTablet = width < TABLET_SCREEN_BREAKPOINT;
const isTablet = useIsTablet();
const showFirstLayer = !isTablet || !selectedSecond;
const showSecondLayer = showFirstLayer && secondLayer.length > 0;
const showThirdLayer = Boolean(selectedSecond);

const timetableTypeRef = useRef(timetableType);

useEffect(() => {
timetableTypeRef.current = timetableType;
}, [timetableType]);

const [searchParams, setSearchParams] = useSearchParams();

const updateSecondLayer = useCallback(
(query: string) => {
TimetableManager.updateLastOpenedInstitute(query);
Toast.promise(TimetableManager.getSecondLayerByType(timetableType, query), "Fetching groups...")
.then(setSecondLayer)
.catch(Toast.error);
},
[timetableType]
);
const updateSecondLayer = useCallback((query: string) => {
TimetableManager.updateLastOpenedInstitute(query);
return Toast.promise(TimetableManager.getSecondLayerByType(timetableTypeRef.current, query), "Fetching groups...")
.then(setSecondLayer)
.catch(Toast.error);
}, []);

const updateThirdLayer = useCallback((major: string) => {
return Toast.promise(
TimetableManager.getThirdLayerByType(timetableTypeRef.current, major),
"Fetching timetables..."
)
.then(setThirdLayer)
.catch(Toast.error);
}, []);

const updateThirdLayer = useCallback(
(major: string) => {
Toast.promise(TimetableManager.getThirdLayerByType(timetableType, major), "Fetching timetables...")
.then(setThirdLayer)
.catch(Toast.error);
},
[timetableType]
);
const reset = useCallback(() => {
setSelectedFirst(null);
setSelectedSecond(null);
setThirdLayer([]);
setSecondLayer([]);
}, []);

// Initial fetch
useEffect(() => {
if (!force && timetableType === "timetable" && searchParams.size === 0) {
TimetableManager.getLastOpenedTimetable().then((t) => {
t && navigate(t);
});
}
reset();

Toast.promise(TimetableManager.getFirstLayerSelectionByType(timetableType), "Fetching institutes...")
.then(setFirstLayer)
Expand All @@ -73,11 +74,13 @@ const HomePage: FC<OwnProps> = ({ timetableType }) => {
return () => {
Toast.hideAllMessages();
};
}, [timetableType, force, navigate, searchParams]);
}, [timetableType, reset]);

// On first layer change
useEffect(() => {
if (!selectedFirst) return;
if (!selectedFirst) {
return;
}
updateSecondLayer(selectedFirst);
}, [selectedFirst, updateSecondLayer]);

Expand All @@ -98,13 +101,18 @@ const HomePage: FC<OwnProps> = ({ timetableType }) => {
setSelectedSecond(null);
setThirdLayer([]);
}
if (secondLayer.includes(selectedMajor)) {
if (firstLayer.includes(selectedInstitute) && secondLayer.includes(selectedMajor)) {
setSelectedSecond(selectedMajor);
}
}, [firstLayer, secondLayer, searchParams]);

useEffect(() => {
const selectedInstitute = searchParams.get("institute") || "";

if (firstLayer.includes(selectedInstitute)) {
setSelectedFirst(selectedInstitute);
}
}, [secondLayer, searchParams, firstLayer]);
}, [firstLayer, searchParams]);

const handleFirstChange = useCallback(
(institute: string | null) => {
Expand Down
16 changes: 16 additions & 0 deletions src/pages/NavigationSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import TimetableManager from "@/utils/data/TimetableManager";
import { useEffect } from "react";
import { useNavigate } from "react-router";

const NavigationSelector = () => {
const navigate = useNavigate();

useEffect(() => {
TimetableManager.getLastOpenedTimetable().then((t) => {
navigate(t || "/home");
});
}, [navigate]);
return null;
};

export default NavigationSelector;
2 changes: 1 addition & 1 deletion src/pages/TimetablePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const TimetablePage: FC<OwnProps> = ({ isExamsTimetable = false }) => {

function onError(e: string, userError?: string) {
Toast.error(e, userError);
navigate("/", { state: { force: true } });
navigate("/home");
}

// biome-ignore lint/correctness/useExhaustiveDependencies: I don't actually remember why but I don't want to break it
Expand Down
2 changes: 1 addition & 1 deletion src/utils/timetable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,6 @@ export const skeletonTimetable = {
export const pathnameToType = (pathname: string): TimetablePageType => {
if (pathname.includes("lecturer")) return "lecturer";
if (pathname.includes("selective")) return "selective";
if (pathname === "/") return "home";
if (pathname.includes("home")) return "home";
return "timetable";
};

0 comments on commit c566d59

Please sign in to comment.