From 3ac6712091e49c2db72a0d57c8200e56ad85e456 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Fri, 4 Apr 2025 21:10:27 +0300 Subject: [PATCH] Move notifications to root loader --- app/components/layout/NotificationPopover.tsx | 34 +++++++++---------- .../components/NotificationList.tsx | 2 +- .../routes/notifications.peek.ts | 19 ----------- app/hooks/swr.ts | 19 ----------- app/root.tsx | 32 ++++------------- app/routes.ts | 4 --- app/utils/urls.ts | 1 - locales/en/common.json | 1 - locales/fr-EU/common.json | 1 - locales/it/common.json | 1 - 10 files changed, 24 insertions(+), 90 deletions(-) delete mode 100644 app/features/notifications/routes/notifications.peek.ts diff --git a/app/components/layout/NotificationPopover.tsx b/app/components/layout/NotificationPopover.tsx index e29a33787..08599d3bc 100644 --- a/app/components/layout/NotificationPopover.tsx +++ b/app/components/layout/NotificationPopover.tsx @@ -1,29 +1,34 @@ -import { useLocation } from "@remix-run/react"; +import { useLocation, useMatches, useRevalidator } from "@remix-run/react"; import clsx from "clsx"; import * as React from "react"; import { useTranslation } from "react-i18next"; -import { useUser } from "~/features/auth/core/user"; import { NotificationItem, NotificationItemDivider, NotificationsList, } from "~/features/notifications/components/NotificationList"; import { NOTIFICATIONS } from "~/features/notifications/notifications-contants"; -import { useNotifications } from "~/hooks/swr"; +import type { RootLoaderData } from "~/root"; import { NOTIFICATIONS_URL } from "~/utils/urls"; import { useMarkNotificationsAsSeen } from "../../features/notifications/notifications-hooks"; -import type { LoaderNotification } from "../../features/notifications/routes/notifications.peek"; import { LinkButton } from "../Button"; import { SendouButton } from "../elements/Button"; import { SendouPopover } from "../elements/Popover"; import { BellIcon } from "../icons/Bell"; import { RefreshIcon } from "../icons/Refresh"; + import styles from "./NotificationPopover.module.css"; +export type LoaderNotification = NonNullable< + RootLoaderData["notifications"] +>[number]; + export function NotificationPopover() { const location = useLocation(); - const user = useUser(); - const { notifications, isLoading, refresh } = useNotifications(); + const [root] = useMatches(); + + const notifications = (root.data as RootLoaderData | undefined) + ?.notifications; const unseenIds = React.useMemo( () => @@ -33,7 +38,7 @@ export function NotificationPopover() { [notifications], ); - if (!user) { + if (!notifications) { return null; } @@ -56,8 +61,6 @@ export function NotificationPopover() { @@ -67,15 +70,12 @@ export function NotificationPopover() { function NotificationContent({ notifications, unseenIds, - refresh, - isLoading, }: { notifications: LoaderNotification[]; unseenIds: number[]; - refresh: () => void; - isLoading: boolean; }) { const { t } = useTranslation(["common"]); + const { revalidate, state } = useRevalidator(); useMarkNotificationsAsSeen(unseenIds); @@ -89,16 +89,14 @@ function NotificationContent({ icon={} variant="minimal" className={styles.refreshButton} - onPress={refresh} - isDisabled={isLoading} + onPress={revalidate} + isDisabled={state !== "idle"} />
{notifications.length === 0 ? (
- {isLoading - ? t("common:notifications.loading") - : t("common:notifications.empty")} + {t("common:notifications.empty")}
) : ( diff --git a/app/features/notifications/components/NotificationList.tsx b/app/features/notifications/components/NotificationList.tsx index 2ed95067f..ea6650f6c 100644 --- a/app/features/notifications/components/NotificationList.tsx +++ b/app/features/notifications/components/NotificationList.tsx @@ -2,11 +2,11 @@ import { Link } from "@remix-run/react"; import { formatDistance } from "date-fns"; import { useTranslation } from "react-i18next"; import { Image } from "~/components/Image"; +import type { LoaderNotification } from "~/components/layout/NotificationPopover"; import { notificationLink, notificationNavIcon, } from "~/features/notifications/notifications-utils"; -import type { LoaderNotification } from "~/features/notifications/routes/notifications.peek"; import { databaseTimestampToDate } from "~/utils/dates"; import { navIconUrl } from "~/utils/urls"; import styles from "./NotificationList.module.css"; diff --git a/app/features/notifications/routes/notifications.peek.ts b/app/features/notifications/routes/notifications.peek.ts deleted file mode 100644 index c3f1cfd5b..000000000 --- a/app/features/notifications/routes/notifications.peek.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { LoaderFunctionArgs } from "@remix-run/node"; -import { requireUser } from "~/features/auth/core/user.server"; -import type { SerializeFrom } from "~/utils/remix"; -import * as NotificationRepository from "../NotificationRepository.server"; -import { NOTIFICATIONS } from "../notifications-contants"; - -export type NotificationsLoaderData = SerializeFrom; -export type LoaderNotification = - NotificationsLoaderData["notifications"][number]; - -export const loader = async ({ request }: LoaderFunctionArgs) => { - const user = await requireUser(request); - - const notifications = await NotificationRepository.findByUserId(user.id, { - limit: NOTIFICATIONS.PEEK_COUNT, - }); - - return { notifications }; -}; diff --git a/app/hooks/swr.ts b/app/hooks/swr.ts index 5bedba5e5..1af229400 100644 --- a/app/hooks/swr.ts +++ b/app/hooks/swr.ts @@ -1,19 +1,15 @@ -import useSWR from "swr"; import useSWRImmutable from "swr/immutable"; import type { EventsWithMapPoolsLoaderData } from "~/features/calendar/routes/map-pool-events"; import type { PatronsListLoaderData } from "~/features/front-page/routes/patrons-list"; -import type { NotificationsLoaderData } from "~/features/notifications/routes/notifications.peek"; import type { TrustersLoaderData } from "~/features/sendouq/routes/trusters"; import type { WeaponUsageLoaderData } from "~/features/sendouq/routes/weapon-usage"; import type { ModeShort, StageId } from "~/modules/in-game-lists"; import { GET_ALL_EVENTS_WITH_MAP_POOLS_ROUTE, GET_TRUSTERS_ROUTE, - NOTIFICATIONS_PEAK_ROUTE, PATRONS_LIST_ROUTE, getWeaponUsage, } from "~/utils/urls"; -import { useUser } from "../features/auth/core/user"; // TODO: replace with useFetcher after proper errr handling is implemented https://github.com/remix-run/react-router/discussions/10013 @@ -83,18 +79,3 @@ export function usePatrons() { isError: error, }; } - -export function useNotifications() { - const user = useUser(); - const { data, error, mutate, isValidating } = useSWR( - user ? NOTIFICATIONS_PEAK_ROUTE : null, - fetcher(NOTIFICATIONS_PEAK_ROUTE), - ); - - return { - notifications: data?.notifications, - isLoading: isValidating || (!error && !data), - isError: error, - refresh: () => mutate(), - }; -} diff --git a/app/root.tsx b/app/root.tsx index 8dbe63b3f..cf445272e 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -15,7 +15,6 @@ import { useMatches, useNavigate, useNavigation, - useRevalidator, useSearchParams, } from "@remix-run/react"; import generalI18next from "i18next"; @@ -25,6 +24,8 @@ import { I18nProvider } from "react-aria-components"; import { ErrorBoundary as ClientErrorBoundary } from "react-error-boundary"; import { useTranslation } from "react-i18next"; import { useChangeLanguage } from "remix-i18next/react"; +import * as NotificationRepository from "~/features/notifications/NotificationRepository.server"; +import { NOTIFICATIONS } from "~/features/notifications/notifications-contants"; import type { SendouRouteHandle } from "~/utils/remix.server"; import { Catcher } from "./components/Catcher"; import { SendouToastRegion, toastQueue } from "./components/elements/Toast"; @@ -42,7 +43,6 @@ import { } from "./features/theme/core/provider"; import { getThemeSession } from "./features/theme/core/session.server"; import { useIsMounted } from "./hooks/useIsMounted"; -import { useVisibilityChange } from "./hooks/useVisibilityChange"; import { DEFAULT_LANGUAGE } from "./modules/i18n/config"; import i18next, { i18nCookie } from "./modules/i18n/i18next.server"; import type { Namespace } from "./modules/i18n/resources.server"; @@ -116,6 +116,11 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { languages: user.languages ? user.languages.split(",") : [], } : undefined, + notifications: user + ? await NotificationRepository.findByUserId(user.id, { + limit: NOTIFICATIONS.PEEK_COUNT, + }) + : undefined, }, { headers: { "Set-Cookie": await i18nCookie.serialize(locale) }, @@ -263,29 +268,6 @@ function usePreloadTranslation() { }, []); } -// @ts-expect-error to be used in the future -function useRevalidateOnRevisit() { - const visibility = useVisibilityChange(); - const { revalidate } = useRevalidator(); - const [lastUpdated, setLastUpdated] = React.useState(); - - React.useEffect(() => { - setLastUpdated(new Date()); - }, []); - - React.useEffect(() => { - if (visibility !== "visible" || !lastUpdated) return; - - const sinceLastUpdated = new Date().getTime() - lastUpdated.getTime(); - - // 15 minutes - if (sinceLastUpdated < 1000 * 60 * 15) return; - - setLastUpdated(new Date()); - revalidate(); - }, [visibility, revalidate, lastUpdated]); -} - function useCustomizedCSSVars() { const matches = useMatches(); diff --git a/app/routes.ts b/app/routes.ts index dd15705e6..f6bfa0778 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -10,10 +10,6 @@ export default [ route("/patrons-list", "features/front-page/routes/patrons-list.ts"), route("/notifications", "features/notifications/routes/notifications.tsx"), - route( - "/notifications/peek", - "features/notifications/routes/notifications.peek.ts", - ), route( "/notifications/seen", "features/notifications/routes/notifications.seen.ts", diff --git a/app/utils/urls.ts b/app/utils/urls.ts index 5675503fc..e31fabc13 100644 --- a/app/utils/urls.ts +++ b/app/utils/urls.ts @@ -140,7 +140,6 @@ export const GET_TRUSTERS_ROUTE = "/trusters"; export const PATRONS_LIST_ROUTE = "/patrons-list"; export const NOTIFICATIONS_URL = "/notifications"; -export const NOTIFICATIONS_PEAK_ROUTE = "/notifications/peek"; export const NOTIFICATIONS_MARK_AS_SEEN_ROUTE = "/notifications/seen"; interface UserLinkArgs { diff --git a/locales/en/common.json b/locales/en/common.json index f2da1340e..6dadc8043 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -43,7 +43,6 @@ "notifications.title": "Notifications", "notifications.empty": "None yet, check back later", - "notifications.loading": "Loading...", "notifications.seeAll": "See all", "notifications.fullList.explanation": "Max of 100 notifications shown. Notifications older than 14 days are automatically deleted.", "notifications.managePush": "Push notifications", diff --git a/locales/fr-EU/common.json b/locales/fr-EU/common.json index 8b771c082..9e9c3f72d 100644 --- a/locales/fr-EU/common.json +++ b/locales/fr-EU/common.json @@ -43,7 +43,6 @@ "notifications.title": "Notification", "notifications.empty": "Rien ici encore, revenez plus tard", - "notifications.loading": "Chargement...", "notifications.seeAll": "Voir tout", "notifications.fullList.explanation": "100 notifications au maximum peuvent être afficher. Les notifications sont effacer automatiquement au bout de 14 jours.", "notifications.managePush": "Notifications push", diff --git a/locales/it/common.json b/locales/it/common.json index e628dc013..0bcc15b2a 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -43,7 +43,6 @@ "notifications.title": "Notifiche", "notifications.empty": "Nessuna notifica, controlla più tardi", - "notifications.loading": "Caricamento...", "notifications.seeAll": "Vedi tutte", "notifications.fullList.explanation": "Un massimo di 100 notifiche sono state mostrate. Le notifiche più vecchie di 14 giorni vengono eliminate automaticamente.", "notifications.managePush": "Notifiche push",