diff --git a/client/src/app/pages/dashboard/users/NotificationsPage.tsx b/client/src/app/pages/dashboard/users/NotificationsPage.tsx new file mode 100644 index 000000000..87f6cd190 --- /dev/null +++ b/client/src/app/pages/dashboard/users/NotificationsPage.tsx @@ -0,0 +1,92 @@ +import { APIFetchV1 } from "util/api"; +import { ErrorPage } from "app/pages/ErrorPage"; +import useSetSubheader from "components/layout/header/useSetSubheader"; +import NotificationRow from "components/notifications/NotificationRow"; +import MiniTable from "components/tables/components/MiniTable"; +import ApiError from "components/util/ApiError"; +import Loading from "components/util/Loading"; +import useApiQuery from "components/util/query/useApiQuery"; +import { UserContext } from "context/UserContext"; +import React, { useContext, useMemo } from "react"; +import { Button, Col, Row } from "react-bootstrap"; +import { NotificationDocument, PublicUserDocument } from "tachi-common"; + +export default function NotificationsPage() { + const { user } = useContext(UserContext); + + useSetSubheader(["Dashboard", "Notifications"]); + + if (!user) { + return ; + } + + return ; +} + +function NotificationsInnerPage({ user }: { user: PublicUserDocument }) { + const { data, error } = useApiQuery>( + `/users/${user.id}/notifications` + ); + + const unread = useMemo(() => { + if (!data) { + return 0; + } + + return data.filter((e) => e.read === false).length; + }, [data]); + + if (error) { + return ; + } + + if (!data) { + return ; + } + + if (data.length === 0) { + return ( +
+
You've got no mail!
+
+ ); + } + + return ( + + + + {data.map((e) => ( + + ))} + + + +
+
+
+ +
+
+ {data.length} Notifications. + {unread > 0 && ( + <> + {" "} + ({unread} Unread) + + )} +
+
+ +
+ ); +} diff --git a/client/src/app/pages/dashboard/users/UserSettingsPage.tsx b/client/src/app/pages/dashboard/users/UserSettingsPage.tsx index 9906c1b46..bd1a54f05 100644 --- a/client/src/app/pages/dashboard/users/UserSettingsPage.tsx +++ b/client/src/app/pages/dashboard/users/UserSettingsPage.tsx @@ -210,9 +210,7 @@ function PreferencesForm({ reqUser }: { reqUser: PublicUserDocument }) { onChange={formik.handleChange} label="Advanced Mode" /> - - Enable advanced stuff, like being able to copy a tables contents into a CSV. - + Enable advanced stuff. {/* + + + + diff --git a/client/src/components/layout/header/Topbar.tsx b/client/src/components/layout/header/Topbar.tsx index 48b4909c6..be3385835 100644 --- a/client/src/components/layout/header/Topbar.tsx +++ b/client/src/components/layout/header/Topbar.tsx @@ -3,6 +3,7 @@ import LinkButton from "components/util/LinkButton"; import { UserContext } from "context/UserContext"; import React, { useContext } from "react"; import SearchBar from "./SearchBar"; +import { UserNotificationButton } from "./UserNotificationButton"; export function Topbar() { const { user } = useContext(UserContext); @@ -13,32 +14,7 @@ export function Topbar() { {user ? ( <> - {/* commented out quick panel stuff for future */} - {/* Quick panel} - > -
-
- - - -
-
-
*/} - + ) : ( diff --git a/client/src/components/layout/header/UserNotificationButton.tsx b/client/src/components/layout/header/UserNotificationButton.tsx new file mode 100644 index 000000000..399c8c93b --- /dev/null +++ b/client/src/components/layout/header/UserNotificationButton.tsx @@ -0,0 +1,35 @@ +import Icon from "components/util/Icon"; +import useApiQuery from "components/util/query/useApiQuery"; +import React, { useMemo } from "react"; +import { Link } from "react-router-dom"; +import { NotificationDocument, PublicUserDocument } from "tachi-common"; + +export function UserNotificationButton({ user }: { user: PublicUserDocument }) { + // don't care whether it errors or not, tbh + const { data } = useApiQuery>(`/users/${user.id}/notifications`); + + const unread = useMemo(() => { + if (!data) { + return 0; + } + + return data.filter((e) => e.read === false).length; + }, [data]); + + return ( +
+ +
+ {unread > 0 ? ( + + ) : ( + + )} +
+ +
+ ); +} diff --git a/client/src/components/layout/header/UserProfileDropdown.tsx b/client/src/components/layout/header/UserProfileDropdown.tsx index 5b6223f7b..627cddf73 100644 --- a/client/src/components/layout/header/UserProfileDropdown.tsx +++ b/client/src/components/layout/header/UserProfileDropdown.tsx @@ -14,7 +14,6 @@ import { PublicUserDocument } from "tachi-common"; export function UserProfileDropdown({ user }: { user: PublicUserDocument }) { const { setUser } = useContext(UserContext); - const [heySplash] = useState(RFA(heySplashes)); return ( @@ -28,7 +27,7 @@ export function UserProfileDropdown({ user }: { user: PublicUserDocument }) { {heySplash}, {" "} - + {user.username} @@ -37,17 +36,6 @@ export function UserProfileDropdown({ user }: { user: PublicUserDocument }) { -
-
- -
-
- {user.username} -
-
- - -
+ +
+
+ +
+
+
Notifications
+
View your notifications!
+
+
+ + + {notif.read ? ( + + ) : ( + + )} + + + + + {notif.title} + + + + + + ); +} + +function NotifToURL(notif: NotificationDocument) { + switch (notif.body.type) { + case "QUEST_CHANGED": + return "/idkyet"; + case "RIVALED_BY": + return `/dashboard/users/${notif.body.content.userID}/games${notif.body.content.game}/${notif.body.content.playtype}`; + } +} diff --git a/client/src/components/util/Icon.tsx b/client/src/components/util/Icon.tsx index 5bd1c93a2..da32036aa 100644 --- a/client/src/components/util/Icon.tsx +++ b/client/src/components/util/Icon.tsx @@ -14,7 +14,7 @@ export default function Icon({ brand?: boolean; regular?: boolean; onClick?: () => void; - colour?: "info" | "primary" | "danger" | "warning"; + colour?: "info" | "primary" | "danger" | "warning" | "success" | "muted"; style?: CSSProperties; }) { return ( diff --git a/common/src/types.ts b/common/src/types.ts index c2cc0ac8b..a22754cc2 100644 --- a/common/src/types.ts +++ b/common/src/types.ts @@ -1595,19 +1595,6 @@ interface BaseNotification { } export type NotificationBody = - | { - type: "CHALLENGE_BEAT"; - content: { - userID: integer; - challenge: ChallengeWallDocument; - }; - } - | { - type: "CHALLENGE_RECEIVED"; - content: { - challenge: ChallengeWallDocument; - }; - } | { type: "QUEST_CHANGED"; // Emitted when a quest the user is subscribed to changed. content: { @@ -1640,15 +1627,6 @@ export interface ChallengeSubscriptionDocument { achievedAt: number | null; } -export interface ChallengeWallDocument { - chartID: string; - authorID: integer; - type: "lamp" | "score"; - - game: Game; - playtype: Playtype; -} - interface BaseImportTracker { timeStarted: number; importID: string; diff --git a/server/src/lib/notifications/notification-wrappers.ts b/server/src/lib/notifications/notification-wrappers.ts index 584649784..9ef1d4994 100644 --- a/server/src/lib/notifications/notification-wrappers.ts +++ b/server/src/lib/notifications/notification-wrappers.ts @@ -4,7 +4,7 @@ import { FormatGame } from "tachi-common"; import type { Game, integer, Playtype, PublicUserDocument } from "tachi-common"; /** - * A utility wrapper for sending the SET_RIVAL notification. + * A utility wrapper for sending the RIVALED_BY notification. * * @param toUserID - The user to send this notification to. * @param fromUser - The user who rivalled them.