Check for expires compelted tutorial cookie.
This commit is contained in:
22
lib/versionStringToNumber.ts
Normal file
22
lib/versionStringToNumber.ts
Normal file
@@ -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;
|
||||||
@@ -12,10 +12,6 @@ interface TutorialProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Tutorial = ({ isLoading }: TutorialProps): JSX.Element => {
|
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 (
|
return (
|
||||||
<VStack
|
<VStack
|
||||||
h="auto"
|
h="auto"
|
||||||
@@ -27,6 +23,7 @@ const Tutorial = ({ isLoading }: TutorialProps): JSX.Element => {
|
|||||||
py={4}
|
py={4}
|
||||||
px={{ base: 0, sm: 2, md: 4 }}
|
px={{ base: 0, sm: 2, md: 4 }}
|
||||||
bg="gray.700"
|
bg="gray.700"
|
||||||
|
borderRadius={{ base: "", sm: "2xl" }}
|
||||||
>
|
>
|
||||||
<TutorialHeading />
|
<TutorialHeading />
|
||||||
<TutorialAboutApp />
|
<TutorialAboutApp />
|
||||||
|
|||||||
@@ -1,12 +1,25 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
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;
|
exp: string;
|
||||||
version: string;
|
version: number;
|
||||||
completed: boolean;
|
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 * //
|
// * Storage Helpers * //
|
||||||
|
|
||||||
const setTempStorage = (storageState: StorageState): void => {
|
const setTempStorage = (storageState: StorageState): void => {
|
||||||
@@ -53,8 +66,8 @@ const tutorialSlice = createSlice({
|
|||||||
reducers: {
|
reducers: {
|
||||||
// Set temp complete
|
// Set temp complete
|
||||||
setTempTutorialComplete(state: TutorialSlice) {
|
setTempTutorialComplete(state: TutorialSlice) {
|
||||||
const exp: string = addMonths(new Date(), 1).toJSON();
|
const exp: string = generateExpDate();
|
||||||
const version: string = process.env.NEXT_PUBLIC_APP_VERSION.split("-")[0];
|
const version: number = generateVersion();
|
||||||
const storageState: StorageState = {
|
const storageState: StorageState = {
|
||||||
exp,
|
exp,
|
||||||
version,
|
version,
|
||||||
@@ -67,8 +80,8 @@ const tutorialSlice = createSlice({
|
|||||||
},
|
},
|
||||||
// Set completed (remember)
|
// Set completed (remember)
|
||||||
setTutorialCompleted(state: TutorialSlice) {
|
setTutorialCompleted(state: TutorialSlice) {
|
||||||
const exp: string = addMonths(new Date(), 1).toJSON();
|
const exp: string = generateExpDate();
|
||||||
const version: string = process.env.NEXT_PUBLIC_APP_VERSION.split("-")[0];
|
const version: number = generateVersion();
|
||||||
const storageState: StorageState = {
|
const storageState: StorageState = {
|
||||||
exp,
|
exp,
|
||||||
version,
|
version,
|
||||||
|
|||||||
@@ -3,29 +3,38 @@ import { Provider } from "react-redux";
|
|||||||
import { store } from "../app/store";
|
import { store } from "../app/store";
|
||||||
import { useAppDispatch, useAppSelector } from "../app/hooks";
|
import { useAppDispatch, useAppSelector } from "../app/hooks";
|
||||||
import { updateLoading } from "../features/calender";
|
import { updateLoading } from "../features/calender";
|
||||||
import { getAndSetTutorial } from "../features/tutorial";
|
import {
|
||||||
|
clearTutorialCompleted,
|
||||||
|
getAndSetTutorial,
|
||||||
|
StorageState
|
||||||
|
} from "../features/tutorial";
|
||||||
import { Box } from "@chakra-ui/react";
|
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 Calender from "../components/calender";
|
||||||
import Tutorial from "../components/tutorial";
|
import Tutorial from "../components/tutorial";
|
||||||
import LoadingOverlay from "../components/loading/LoadingOverlay";
|
import LoadingOverlay from "../components/loading/LoadingOverlay";
|
||||||
|
import versionStringToNumber from "../../lib/versionStringToNumber";
|
||||||
|
|
||||||
const IndexPage = (): JSX.Element => {
|
const IndexPage = (): JSX.Element => {
|
||||||
const isLoading = useAppSelector((state) => state.calender.isLoading);
|
const currDateStr: string = useAppSelector(
|
||||||
const completedTutorial = 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
|
(state) => state.tutorial.completedTutorial
|
||||||
);
|
);
|
||||||
const tutorialCompletionInfo = useAppSelector(
|
const tutorialCompletionInfo: StorageState = useAppSelector(
|
||||||
(state) => state.tutorial.storageState
|
(state) => state.tutorial.storageState
|
||||||
);
|
);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const currDate = useRef<UpdateCalenderPropsDateLayout>({
|
// Get the completed tutorial cookie or have it set to false.
|
||||||
year: parseInt(format(new Date(), "y")),
|
|
||||||
month: parseInt(format(new Date(), "M")),
|
|
||||||
day: parseInt(format(new Date(), "d"))
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (completedTutorial === null && tutorialCompletionInfo === null) {
|
if (completedTutorial === null && tutorialCompletionInfo === null) {
|
||||||
dispatch(getAndSetTutorial());
|
dispatch(getAndSetTutorial());
|
||||||
@@ -36,6 +45,73 @@ const IndexPage = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
}, [completedTutorial, dispatch, tutorialCompletionInfo]);
|
}, [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<UpdateCalenderPropsDateLayout>({
|
||||||
|
year: parseInt(format(currDateObj, "y")),
|
||||||
|
month: parseInt(format(currDateObj, "M")),
|
||||||
|
day: parseInt(format(currDateObj, "d"))
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box textAlign="center" w="100%" h="auto" pt="50px" minWidth="min-content">
|
<Box textAlign="center" w="100%" h="auto" pt="50px" minWidth="min-content">
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
|||||||
Reference in New Issue
Block a user