diff --git a/apps/feeds/components/FeedLink.tsx b/apps/feeds/components/FeedLink.tsx
index 12437b4a6..4855ad0e8 100644
--- a/apps/feeds/components/FeedLink.tsx
+++ b/apps/feeds/components/FeedLink.tsx
@@ -5,7 +5,7 @@ import Grid from "@mui/material/Grid"
import Card from "@mui/material/Card"
import CardActionArea from "@mui/material/CardActionArea"
import CardContent from "@mui/material/CardContent"
-import Image from 'next/image'
+import Image from "next/image"
import React, { useEffect, useState } from "react"
import { FeedInfo } from "../constants/types"
import { Lang, LangMap } from "../constants/langs"
@@ -38,14 +38,14 @@ const FeedLink = ({ name, feed, locale }: Props) => {
}
}
}
- console.log(coverImg);
+
setCover(coverImg)
}, [])
return (
- {
-
- {cover &&
}
+
+ {cover && (
+
+ )}
diff --git a/apps/forum/src/components/App.tsx b/apps/forum/src/components/App.tsx
index efff15e16..914e1e859 100644
--- a/apps/forum/src/components/App.tsx
+++ b/apps/forum/src/components/App.tsx
@@ -12,6 +12,7 @@ import FeedBackBox from "./FeedBackBox";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import { API } from "@aws-amplify/api";
import { getUserAttr, getIdToken, LoadingSpinner } from "wasedatime-ui";
+import { storeDate } from "@app/utils/storeDate";
const App = () => {
return (
@@ -67,6 +68,7 @@ const InnerApp = () => {
useEffect(() => {
fetchNotification();
+ storeDate();
}, []);
return (
diff --git a/apps/forum/src/utils/getDate.ts b/apps/forum/src/utils/getDate.ts
new file mode 100644
index 000000000..c4ae2f6c6
--- /dev/null
+++ b/apps/forum/src/utils/getDate.ts
@@ -0,0 +1,15 @@
+export const getCurrentDateInJST = () => {
+ const date = new Date();
+ const jstOffset = 9 * 60; // JST is UTC+9
+ const localOffset = date.getTimezoneOffset();
+ date.setMinutes(date.getMinutes() + localOffset + jstOffset);
+
+ const YYYY = date.getFullYear();
+ const MM = String(date.getMonth() + 1).padStart(2, "0"); // Months are 0-based
+ const DD = String(date.getDate()).padStart(2, "0");
+ const HH = String(date.getHours()).padStart(2, "0");
+ const mm = String(date.getMinutes()).padStart(2, "0");
+ const SS = String(date.getSeconds()).padStart(2, "0");
+
+ return `${YYYY}${MM}${DD}${HH}${mm}${SS}`;
+};
diff --git a/apps/forum/src/utils/storeDate.ts b/apps/forum/src/utils/storeDate.ts
new file mode 100644
index 000000000..513e19fc5
--- /dev/null
+++ b/apps/forum/src/utils/storeDate.ts
@@ -0,0 +1,14 @@
+import { getCurrentDateInJST } from "./getDate";
+
+export const storeDate = () => {
+ const storedDateInJST = localStorage.getItem("lastCheckedDateJST");
+ const currentDateInJST = getCurrentDateInJST();
+
+ if (!storedDateInJST) {
+ // If there's no stored date, set the current date to local storage.
+ localStorage.setItem("lastCheckedDateJST", currentDateInJST);
+ } else if (storedDateInJST !== currentDateInJST) {
+ // If the stored date and the current date are different, update the stored date.
+ localStorage.setItem("lastCheckedDateJST", currentDateInJST);
+ }
+};
diff --git a/apps/root/package.json b/apps/root/package.json
index 181bf874b..63cda7caf 100644
--- a/apps/root/package.json
+++ b/apps/root/package.json
@@ -56,6 +56,7 @@
"vite-plugin-pwa": "0.12.0"
},
"dependencies": {
+ "@aws-amplify/api": "4.0.42",
"@aws-amplify/auth": "4.5.6",
"@aws-amplify/core": "4.5.6",
"@emotion/react": "11.9.0",
diff --git a/apps/root/pnpm-lock.yaml b/apps/root/pnpm-lock.yaml
index 549cf601f..0c9a2f492 100644
--- a/apps/root/pnpm-lock.yaml
+++ b/apps/root/pnpm-lock.yaml
@@ -5,6 +5,9 @@ settings:
excludeLinksFromLockfile: false
dependencies:
+ '@aws-amplify/api':
+ specifier: 4.0.42
+ version: 4.0.42(react-native@0.68.2)
'@aws-amplify/auth':
specifier: 4.5.6
version: 4.5.6(react-native@0.68.2)
@@ -262,6 +265,44 @@ packages:
leven: 3.1.0
dev: true
+ /@aws-amplify/api-graphql@2.3.6(react-native@0.68.2):
+ resolution: {integrity: sha512-bAFApP7Yw2uLythEG4og0nDm8xVqqEMLKiT5AUpElKmlzU5t106gYissRLet+oHeiE2DMnIWCL9g/rOn93Z1NQ==}
+ dependencies:
+ '@aws-amplify/api-rest': 2.0.42(react-native@0.68.2)
+ '@aws-amplify/auth': 4.5.6(react-native@0.68.2)
+ '@aws-amplify/cache': 4.0.44(react-native@0.68.2)
+ '@aws-amplify/core': 4.5.6(react-native@0.68.2)
+ '@aws-amplify/pubsub': 4.4.3(react-native@0.68.2)
+ graphql: 15.8.0
+ uuid: 3.4.0
+ zen-observable-ts: 0.8.19
+ transitivePeerDependencies:
+ - debug
+ - encoding
+ - react-native
+ dev: false
+
+ /@aws-amplify/api-rest@2.0.42(react-native@0.68.2):
+ resolution: {integrity: sha512-37BUCnI1PM273jUwWceEZVi0frO0mHY8dY/9EZ2zUmA6Rbscbm5ZpyB8E+txtO+odbbBxnU5JHdc7JnWr7Fwpw==}
+ dependencies:
+ '@aws-amplify/core': 4.5.6(react-native@0.68.2)
+ axios: 0.21.4
+ transitivePeerDependencies:
+ - debug
+ - react-native
+ dev: false
+
+ /@aws-amplify/api@4.0.42(react-native@0.68.2):
+ resolution: {integrity: sha512-h+nm6Frbu4G2Ftc7s0SVdQEoLOE9tVlfRM6q0JrQ48Ilu1myQdQ3wormWUxdhlpkuTq+UOYcDTcIUeiD3CC2mA==}
+ dependencies:
+ '@aws-amplify/api-graphql': 2.3.6(react-native@0.68.2)
+ '@aws-amplify/api-rest': 2.0.42(react-native@0.68.2)
+ transitivePeerDependencies:
+ - debug
+ - encoding
+ - react-native
+ dev: false
+
/@aws-amplify/auth@4.5.6(react-native@0.68.2):
resolution: {integrity: sha512-G0pxqKaVouuhVK6qH0EBHG5ziNVRT7AzKSj8jN2I7klHtFJHnY/F6NEEjr/e99IgbyzspjWX5xFRBAQecgF44A==}
dependencies:
@@ -297,6 +338,21 @@ packages:
- react-native
dev: false
+ /@aws-amplify/pubsub@4.4.3(react-native@0.68.2):
+ resolution: {integrity: sha512-bpjucdYHpnrz0fq+0PZ/UfaUR67z7RUm13iDL3GnX8TMCRTTaquOF7S2Sfn9zlWzwDOFzrne6JAl8lnwBmjuEw==}
+ dependencies:
+ '@aws-amplify/auth': 4.5.6(react-native@0.68.2)
+ '@aws-amplify/cache': 4.0.44(react-native@0.68.2)
+ '@aws-amplify/core': 4.5.6(react-native@0.68.2)
+ graphql: 15.8.0
+ paho-mqtt: 1.1.0
+ uuid: 3.4.0
+ zen-observable-ts: 0.8.19
+ transitivePeerDependencies:
+ - encoding
+ - react-native
+ dev: false
+
/@aws-crypto/ie11-detection@1.0.0:
resolution: {integrity: sha512-kCKVhCF1oDxFYgQrxXmIrS5oaWulkvRcPz+QBDMsUr2crbF4VGgGT6+uQhSwJFdUAQ2A//Vq+uT83eJrkzFgXA==}
dependencies:
@@ -3564,6 +3620,14 @@ packages:
engines: {node: '>= 0.4'}
dev: true
+ /axios@0.21.4:
+ resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
+ dependencies:
+ follow-redirects: 1.15.3
+ transitivePeerDependencies:
+ - debug
+ dev: false
+
/babel-core@7.0.0-bridge.0(@babel/core@7.18.2):
resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==}
peerDependencies:
@@ -5037,6 +5101,16 @@ packages:
resolution: {integrity: sha512-1gIBiWJNR0tKUNv8gZuk7l9rVX06OuLzY9AoGio7y/JT4V1IZErEMEq2TJS+PFcw/y0RshZ1J/27VfK1UQzYVg==}
engines: {node: '>=0.4.0'}
+ /follow-redirects@1.15.3:
+ resolution: {integrity: sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ debug: '*'
+ peerDependenciesMeta:
+ debug:
+ optional: true
+ dev: false
+
/for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
dependencies:
@@ -5210,6 +5284,11 @@ packages:
/graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+ /graphql@15.8.0:
+ resolution: {integrity: sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==}
+ engines: {node: '>= 10.x'}
+ dev: false
+
/has-bigints@1.0.2:
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
dev: true
@@ -6745,6 +6824,10 @@ packages:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
+ /paho-mqtt@1.1.0:
+ resolution: {integrity: sha512-KPbL9KAB0ASvhSDbOrZBaccXS+/s7/LIofbPyERww8hM5Ko71GUJQ6Nmg0BWqj8phAIT8zdf/Sd/RftHU9i2HA==}
+ dev: false
+
/param-case@3.0.4:
resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
dependencies:
diff --git a/apps/root/src/components/aboutUs/MeetOurTeam/MeetOurTeam.tsx b/apps/root/src/components/aboutUs/MeetOurTeam/MeetOurTeam.tsx
index 99ed0052f..81b916fc8 100644
--- a/apps/root/src/components/aboutUs/MeetOurTeam/MeetOurTeam.tsx
+++ b/apps/root/src/components/aboutUs/MeetOurTeam/MeetOurTeam.tsx
@@ -35,10 +35,6 @@ const MeetOurTeam = () => {
const { t } = useTranslation()
const [activeCardName, setActiveCardName] = useState("")
- useEffect(() => {
- console.log(activeCardName)
- }, [activeCardName])
-
return (
diff --git a/apps/root/src/components/block/IconTextGroup.tsx b/apps/root/src/components/block/IconTextGroup.tsx
index 2c4e47ca0..009820588 100644
--- a/apps/root/src/components/block/IconTextGroup.tsx
+++ b/apps/root/src/components/block/IconTextGroup.tsx
@@ -19,6 +19,7 @@ interface IconTextGroupProps {
iconPath?: string
expanded?: boolean
currentPath?: string
+ tooltip?: string
}
const NavItemBlock = styled.div`
@@ -62,6 +63,7 @@ export const IconTextGroup = ({
iconPath,
expanded,
currentPath,
+ tooltip,
}: IconTextGroupProps) => {
const [isHover, setIsHover] = useState(false)
return (
@@ -72,17 +74,27 @@ export const IconTextGroup = ({
onMouseOver={() => setIsHover(true)}
onMouseOut={() => setIsHover(false)}
>
-
- {isHover && hoverIcon ? hoverIcon : icon}
+
+ {" "}
+ {/* This is the wrapper */}
+
+ {isHover && hoverIcon ? hoverIcon : icon}
+
+ {text && (
+
+ {text}
+
+ )}
+ {/* Tooltip rendering */}
+ {tooltip && expanded && (
+
+ {tooltip}
+
+ )}
- {text && (
-
- {text}
-
- )}
)
}
diff --git a/apps/root/src/components/frame/MobileNav.tsx b/apps/root/src/components/frame/MobileNav.tsx
index 857c768c9..c4b714112 100644
--- a/apps/root/src/components/frame/MobileNav.tsx
+++ b/apps/root/src/components/frame/MobileNav.tsx
@@ -27,11 +27,18 @@ const MobileNav = ({ navItems, openSignInModal }: Props) => {
className="flex-1 text-center"
customOnClick={() => setCurrentPath(item.path)}
>
-
- {item.icon}
-
-
- {item.name}
+
+
+ {item.icon}
+
+
+ {item.name}
+
+ {item.tooltip && (
+
+ {item.tooltip}
+
+ )}
))
diff --git a/apps/root/src/components/frame/Nav.tsx b/apps/root/src/components/frame/Nav.tsx
index e46190463..981982898 100644
--- a/apps/root/src/components/frame/Nav.tsx
+++ b/apps/root/src/components/frame/Nav.tsx
@@ -26,6 +26,13 @@ import {
TimetableIconHovered,
} from "@app/components/icons/TimetableIcon"
import { ThemeContext, ThemeProvider } from "@app/utils/theme-context"
+import {
+ getCurrentDateInJST,
+ getCurrentDateInUTC,
+ extractDate,
+} from "@app/utils/getDate"
+import { shouldCallApi } from "@app/utils/shouldCallApi"
+import { fetchNotificaiton } from "@app/utils/fetchNotification"
const Sidebar = lazy(() => import("@app/components/frame/Sidebar"))
const MobileNav = lazy(() => import("@app/components/frame/MobileNav"))
@@ -37,6 +44,7 @@ export interface NavItemsProps {
path: string
icon: ReactNode
iconHovered?: ReactNode
+ tooltip?: string
}
const Nav = () => {
@@ -50,6 +58,7 @@ const Nav = () => {
const { t, i18n } = useTranslation()
useEffect(() => {
+ // fetchNotificaiton("")
window.onstorage = () => {
i18n.changeLanguage(localStorage.getItem("wasedatime-lng"))
}
@@ -60,6 +69,52 @@ const Nav = () => {
page_path: page_path,
})
+ const fetchNotificationAndUpdateState = async () => {
+ try {
+ const storedDateInJST = localStorage.getItem("lastCheckedDateJST")
+ const currentDateInJST = getCurrentDateInJST()
+ const storedDateOnly = extractDate(storedDateInJST || "")
+ const currentDateOnly = extractDate(currentDateInJST)
+
+ if (!storedDateOnly) {
+ localStorage.setItem("lastCheckedDateJST", currentDateInJST)
+ } else if (shouldCallApi()) {
+ const newPostsCount = await fetchNotificaiton(storedDateInJST || "")
+ const updatedNavItems = navItems.map((item) =>
+ item.path === "/forum"
+ ? {
+ ...item,
+ tooltip:
+ newPostsCount > 0
+ ? `${newPostsCount} unread posts!`
+ : "Share something in WTF!",
+ }
+ : item
+ )
+ setNavItems(updatedNavItems)
+ localStorage.setItem("lastCheckedDateJST", currentDateInJST)
+ localStorage.setItem(
+ "lastApiCallTimestamp",
+ new Date().getTime().toString()
+ )
+ } else {
+ const updatedNavItems = navItems.map((item) =>
+ item.path === "/forum"
+ ? {
+ ...item,
+ tooltip: "Check out WTF!",
+ }
+ : item
+ )
+ setNavItems(updatedNavItems)
+ }
+ } catch (error) {
+ console.error("Error fetching notifications:", error)
+ }
+ }
+
+ fetchNotificationAndUpdateState()
+
return history.listen(({ location, action }) => {
if (action === "POP") {
const new_page_path = location.pathname + location.search
@@ -71,7 +126,9 @@ const Nav = () => {
})
}, [])
- const navItems: NavItemsProps[] = [
+ // const currentDateInUTC = getCurrentDateInUTC()
+
+ const [navItems, setNavItems] = useState
([
{
name: t("navigation.timetable"),
path: "/courses/timetable",
@@ -89,6 +146,7 @@ const Nav = () => {
path: "/forum",
icon: ,
iconHovered: ,
+ tooltip: "Check out the new posts!",
},
// {
// name: t("navigation.campus"),
@@ -102,7 +160,41 @@ const Nav = () => {
// icon: ,
// iconHovered: ,
// },
- ]
+ ])
+
+ // const navItems: NavItemsProps[] = [
+ // {
+ // name: t("navigation.timetable"),
+ // path: "/courses/timetable",
+ // icon: ,
+ // iconHovered: ,
+ // },
+ // {
+ // name: t("navigation.syllabus"),
+ // path: "/courses/syllabus",
+ // icon: ,
+ // iconHovered: ,
+ // },
+ // {
+ // name: t("navigation.forum"),
+ // path: "/forum",
+ // icon: ,
+ // iconHovered: ,
+ // tooltip: "Check out the new posts!",
+ // },
+ // {
+ // name: t("navigation.campus"),
+ // path: "/campus",
+ // icon: ,
+ // iconHovered: ,
+ // },
+ // {
+ // name: t("navigation.feeds"),
+ // path: "/feeds",
+ // icon: ,
+ // iconHovered: ,
+ // },
+ // ]
return (
diff --git a/apps/root/src/components/frame/Sidebar.tsx b/apps/root/src/components/frame/Sidebar.tsx
index 91528b110..8a58f68b8 100644
--- a/apps/root/src/components/frame/Sidebar.tsx
+++ b/apps/root/src/components/frame/Sidebar.tsx
@@ -115,6 +115,7 @@ const Sidebar = ({ navItems, openSignInModal }: SidebarProps) => {
iconPath={item.path}
expanded={expanded}
currentPath={currentPath}
+ tooltip={item.tooltip}
/>
))}
diff --git a/apps/root/src/utils/fetchNotification.ts b/apps/root/src/utils/fetchNotification.ts
new file mode 100644
index 000000000..88a57a77a
--- /dev/null
+++ b/apps/root/src/utils/fetchNotification.ts
@@ -0,0 +1,18 @@
+import { API } from "@aws-amplify/api"
+
+export const fetchNotificaiton = async (lastCheckDate: string) => {
+ const res = await API.get(
+ "wasedatime-dev",
+ `/forum/notify?lastChecked=${lastCheckDate}`,
+ {
+ headers: {
+ "Content-Type": "application/json",
+ },
+ response: true,
+ }
+ )
+
+ const threadCount = res.data.data
+
+ return threadCount
+}
diff --git a/apps/root/src/utils/getDate.ts b/apps/root/src/utils/getDate.ts
new file mode 100644
index 000000000..0ae37cae6
--- /dev/null
+++ b/apps/root/src/utils/getDate.ts
@@ -0,0 +1,30 @@
+export const getCurrentDateInJST = () => {
+ const date = new Date()
+ const jstOffset = 9 * 60 // JST is UTC+9
+ const localOffset = date.getTimezoneOffset()
+ date.setMinutes(date.getMinutes() + localOffset + jstOffset)
+
+ const YYYY = date.getFullYear()
+ const MM = String(date.getMonth() + 1).padStart(2, "0") // Months are 0-based
+ const DD = String(date.getDate()).padStart(2, "0")
+ const HH = String(date.getHours()).padStart(2, "0")
+ const mm = String(date.getMinutes()).padStart(2, "0")
+ const SS = String(date.getSeconds()).padStart(2, "0")
+
+ return `${YYYY}${MM}${DD}${HH}${mm}${SS}`
+}
+
+export const extractDate = (fullDate: string) => {
+ return fullDate.substring(0, 8) // Extracts the first 8 characters (YYYYMMDD)
+}
+
+export const getCurrentDateInUTC = () => {
+ const date = new Date()
+ const YYYY = date.getUTCFullYear()
+ const MM = String(date.getUTCMonth() + 1).padStart(2, "0")
+ const DD = String(date.getUTCDate()).padStart(2, "0")
+ const HH = String(date.getUTCHours()).padStart(2, "0")
+ const mm = String(date.getUTCMinutes()).padStart(2, "0")
+ const SS = String(date.getUTCSeconds()).padStart(2, "0")
+ return `${YYYY}${MM}${DD}${HH}${mm}${SS}`
+}
diff --git a/apps/root/src/utils/shouldCallApi.ts b/apps/root/src/utils/shouldCallApi.ts
new file mode 100644
index 000000000..80ad15ffb
--- /dev/null
+++ b/apps/root/src/utils/shouldCallApi.ts
@@ -0,0 +1,26 @@
+const LAST_API_CALL_TIMESTAMP = "lastApiCallTimestamp"
+const ONE_HOUR_IN_MS = 60 * 60 * 1000
+
+const convertToTimestamp = (datetime: string) => {
+ const year = parseInt(datetime.substring(0, 4), 10)
+ const month = parseInt(datetime.substring(4, 6), 10) - 1 // Months in JS are 0-indexed
+ const day = parseInt(datetime.substring(6, 8), 10)
+ const hour = parseInt(datetime.substring(8, 10), 10)
+ const minute = parseInt(datetime.substring(10, 12), 10)
+ const second = parseInt(datetime.substring(12, 14), 10)
+
+ return new Date(year, month, day, hour, minute, second).getTime()
+}
+
+export const shouldCallApi = () => {
+ const lastCallTimestamp = localStorage.getItem("lastApiCallTimestamp")
+
+ if (!lastCallTimestamp) {
+ return true
+ }
+
+ const timeSinceLastCall =
+ new Date().getTime() - parseInt(lastCallTimestamp, 10)
+
+ return timeSinceLastCall > ONE_HOUR_IN_MS
+}
diff --git a/apps/root/src/wasedatime-root-config.ts b/apps/root/src/wasedatime-root-config.ts
index 07b647f77..5358defc2 100644
--- a/apps/root/src/wasedatime-root-config.ts
+++ b/apps/root/src/wasedatime-root-config.ts
@@ -16,10 +16,24 @@ import Nav from "@app/components/frame/Nav"
import { registerSW } from "virtual:pwa-register"
+import { API } from "@aws-amplify/api"
+
if (import.meta.env.MODE !== "development" && "serviceWorker" in navigator) {
registerSW()
}
+const apiConfig = {
+ API: {
+ endpoints: [
+ {
+ name: "wasedatime-dev",
+ endpoint: import.meta.env.VITE_API_BASE_URL,
+ },
+ ],
+ },
+}
+API.configure(apiConfig)
+
const routes = constructRoutes(document.querySelector("#single-spa-layout"))
const applications = constructApplications({
routes,