From 9c187301269351bff94e4cd0711b057e1ce92691 Mon Sep 17 00:00:00 2001 From: Lucid Kobold <72232219+LucidKobold@users.noreply.github.com> Date: Thu, 23 Jun 2022 17:20:05 -0500 Subject: [PATCH] Check for expires compelted tutorial cookie. --- lib/versionStringToNumber.ts | 22 +++++++ src/components/tutorial/index.tsx | 5 +- src/features/tutorial/index.ts | 27 ++++++--- src/pages/index.tsx | 98 +++++++++++++++++++++++++++---- 4 files changed, 130 insertions(+), 22 deletions(-) create mode 100644 lib/versionStringToNumber.ts diff --git a/lib/versionStringToNumber.ts b/lib/versionStringToNumber.ts new file mode 100644 index 0000000..492f161 --- /dev/null +++ b/lib/versionStringToNumber.ts @@ -0,0 +1,22 @@ +/** + * Function to convert the version string to a number tha represents the most recent major release. + * @param {string }version The version string. + * @returns {number} a number that represents the most recent major release. + */ +const versionStringToNumber = (version: string): number => { + const versionStrArr: string[] = version.split("."); + + const versionArr: number[] = versionStrArr.map((str) => parseInt(str)); + + if (versionArr[0] === 0 && versionArr[1] === 0 && versionArr[2] > 1) { + versionArr[1] = 1; + } + + const versionStr = `${versionArr[0]}` + "." + `${versionArr[1]}`; + + const versionNum: number = parseFloat(versionStr); + + return versionNum; +}; + +export default versionStringToNumber; diff --git a/src/components/tutorial/index.tsx b/src/components/tutorial/index.tsx index 1d63ac4..3ad41b0 100644 --- a/src/components/tutorial/index.tsx +++ b/src/components/tutorial/index.tsx @@ -12,10 +12,6 @@ interface TutorialProps { } const Tutorial = ({ isLoading }: TutorialProps): JSX.Element => { - // TODO: Add an expiration validator. - // TODO: Add a version validator that removed the completed tutorial storages when there were major changes to the tutorial. - // * The changes are tracked via env variables. The last version that user saw the tutorial is saved in storage. - return ( { py={4} px={{ base: 0, sm: 2, md: 4 }} bg="gray.700" + borderRadius={{ base: "", sm: "2xl" }} > diff --git a/src/features/tutorial/index.ts b/src/features/tutorial/index.ts index fc50ff8..394f5fc 100644 --- a/src/features/tutorial/index.ts +++ b/src/features/tutorial/index.ts @@ -1,12 +1,25 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { addMonths } from "date-fns"; +import { addMonths, endOfDay } from "date-fns"; +import versionStringToNumber from "../../../lib/versionStringToNumber"; -interface StorageState { +export interface StorageState { exp: string; - version: string; + version: number; completed: boolean; } +const endOfToday: Date = endOfDay(new Date()); + +const generateExpDate = (): string => { + return endOfDay(addMonths(endOfToday, 1)).toJSON(); +}; + +const generateVersion = (): number => { + const versionStr: string = process.env.NEXT_PUBLIC_APP_VERSION; + + return versionStringToNumber(versionStr); +}; + // * Storage Helpers * // const setTempStorage = (storageState: StorageState): void => { @@ -53,8 +66,8 @@ const tutorialSlice = createSlice({ reducers: { // Set temp complete setTempTutorialComplete(state: TutorialSlice) { - const exp: string = addMonths(new Date(), 1).toJSON(); - const version: string = process.env.NEXT_PUBLIC_APP_VERSION.split("-")[0]; + const exp: string = generateExpDate(); + const version: number = generateVersion(); const storageState: StorageState = { exp, version, @@ -67,8 +80,8 @@ const tutorialSlice = createSlice({ }, // Set completed (remember) setTutorialCompleted(state: TutorialSlice) { - const exp: string = addMonths(new Date(), 1).toJSON(); - const version: string = process.env.NEXT_PUBLIC_APP_VERSION.split("-")[0]; + const exp: string = generateExpDate(); + const version: number = generateVersion(); const storageState: StorageState = { exp, version, diff --git a/src/pages/index.tsx b/src/pages/index.tsx index ae40e53..1815d30 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -3,29 +3,38 @@ import { Provider } from "react-redux"; import { store } from "../app/store"; import { useAppDispatch, useAppSelector } from "../app/hooks"; import { updateLoading } from "../features/calender"; -import { getAndSetTutorial } from "../features/tutorial"; +import { + clearTutorialCompleted, + getAndSetTutorial, + StorageState +} from "../features/tutorial"; import { Box } from "@chakra-ui/react"; -import { format } from "date-fns"; +import { format, isAfter, isBefore, startOfDay } from "date-fns"; import Calender from "../components/calender"; import Tutorial from "../components/tutorial"; import LoadingOverlay from "../components/loading/LoadingOverlay"; +import versionStringToNumber from "../../lib/versionStringToNumber"; const IndexPage = (): JSX.Element => { - const isLoading = useAppSelector((state) => state.calender.isLoading); - const completedTutorial = useAppSelector( + const currDateStr: string = useAppSelector( + (state) => state.calender.currDate + ); + const isLoading: boolean = useAppSelector( + (state) => state.calender.isLoading + ); + + const currDateObj: Date = new Date(currDateStr); + + // * Tutorial * // + const completedTutorial: boolean = useAppSelector( (state) => state.tutorial.completedTutorial ); - const tutorialCompletionInfo = useAppSelector( + const tutorialCompletionInfo: StorageState = useAppSelector( (state) => state.tutorial.storageState ); const dispatch = useAppDispatch(); - const currDate = useRef({ - year: parseInt(format(new Date(), "y")), - month: parseInt(format(new Date(), "M")), - day: parseInt(format(new Date(), "d")) - }); - + // Get the completed tutorial cookie or have it set to false. useEffect(() => { if (completedTutorial === null && tutorialCompletionInfo === null) { dispatch(getAndSetTutorial()); @@ -36,6 +45,73 @@ const IndexPage = (): JSX.Element => { } }, [completedTutorial, dispatch, tutorialCompletionInfo]); + // Checking the exp date of completed tutorial cookie and if the version completed is out of date. + useEffect(() => { + if (tutorialCompletionInfo !== null) { + const { exp, version } = tutorialCompletionInfo; + const currDateObj: Date = new Date(currDateStr); + + /** + * Checks if the completed tutorial cookie is expired. + * @param {Date} expDate the date when the completed tutorital cookie expires. + * @returns {boolean} true if the cookie is expired, false is otherwise. + */ + const expDateValidator = (expDate: Date): boolean => { + let flag = false; + + const startOfToday = startOfDay(currDateObj); + + if (isAfter(startOfToday, expDate)) { + flag = true; + } + + return flag; + }; + + /** + * Checks if the last time the completed tutorial is before an update to the tutorial. + * @param {number} lastVersionCompleted the version number the tutorial was last completed. + * @returns {boolean} true if the version given is before the changes to the tutorial, false otherwise. + */ + const versionValidator = (lastVersionCompleted: number): boolean => { + const lastVersionWithChangeStr: string = + process.env.NEXT_PUBLIC_NEW_TUTORIAL_VERSION; + const lastVersionWithChange: number = versionStringToNumber( + lastVersionWithChangeStr + ); + + const lastUpdatedDateStr: string = + process.env.NEXT_PUBLIC_LAST_UPDATE_DATE; + const lastUpdatedDate: Date = new Date(lastUpdatedDateStr); + + let flag = false; + + if ( + lastVersionCompleted < lastVersionWithChange || + (lastVersionCompleted === lastVersionWithChange && + isBefore(currDateObj, lastUpdatedDate)) + ) { + flag = true; + console.error("Completed cookie version is out of date."); + } + + return flag; + }; + + if (expDateValidator(new Date(exp)) || versionValidator(version)) { + console.warn("Version outdated or cookie expired."); + dispatch(clearTutorialCompleted()); + } + } + }, [currDateStr, dispatch, tutorialCompletionInfo]); + + // Current date + const currDate = useRef({ + year: parseInt(format(currDateObj, "y")), + month: parseInt(format(currDateObj, "M")), + day: parseInt(format(currDateObj, "d")) + }); + return (