From b714e8c596cc697a77853e2532fabf07649620c6 Mon Sep 17 00:00:00 2001
From: Luna <105922175+ImLunaHey@users.noreply.github.com>
Date: Fri, 20 Dec 2024 12:03:00 +1030
Subject: [PATCH] feat: basic notifications (#9)
---
src/components/Navbar.tsx | 8 +-
src/i18n/lang/en.ts | 11 +++
src/lib/bluesky/hooks/useNotifications.ts | 17 ++++
src/routeTree.gen.ts | 26 +++++
src/routes/notifications.tsx | 112 ++++++++++++++++++++++
5 files changed, 173 insertions(+), 1 deletion(-)
create mode 100644 src/lib/bluesky/hooks/useNotifications.ts
create mode 100644 src/routes/notifications.tsx
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx
index 44fdbe0..a0d3bb0 100644
--- a/src/components/Navbar.tsx
+++ b/src/components/Navbar.tsx
@@ -3,6 +3,11 @@ import { useAuth } from '../lib/bluesky/hooks/useAuth';
import { Link } from './ui/Link';
import { useBlueskyStore } from '../lib/bluesky/store';
+const NotificationsLink = () => {
+ const { t } = useTranslation('notifications');
+ return {t('notifications')};
+};
+
const ProfileLink = () => {
const { session } = useBlueskyStore();
const { t } = useTranslation('profile');
@@ -48,7 +53,8 @@ export const Navbar = () => {
{t('appName')}
-
+ {isAuthenticated &&
}
+ {isAuthenticated &&
}
{isAuthenticated ?
:
}
diff --git a/src/i18n/lang/en.ts b/src/i18n/lang/en.ts
index 0cec618..87e35fe 100644
--- a/src/i18n/lang/en.ts
+++ b/src/i18n/lang/en.ts
@@ -81,4 +81,15 @@ export const en = {
debug: {
notImplemented: 'NOT IMPLEMENTED: {{value}}',
},
+ notifications: {
+ notifications: 'notifications',
+ noNotifications: 'no notifications',
+ followedYou: 'followed you',
+ likedYourPost: 'liked your post',
+ repostedYourPost: 'reposted your post',
+ repliedToYourPost: 'replied to your post',
+ mentionedYou: 'mentioned you',
+ quotedYourPost: 'quoted your post',
+ joinedYourStarterpack: 'joined your starterpack',
+ },
} as const;
diff --git a/src/lib/bluesky/hooks/useNotifications.ts b/src/lib/bluesky/hooks/useNotifications.ts
new file mode 100644
index 0000000..4c7f146
--- /dev/null
+++ b/src/lib/bluesky/hooks/useNotifications.ts
@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+import { useBlueskyStore } from '../store';
+
+export function useNotifications() {
+ const { agent } = useBlueskyStore();
+
+ return useQuery({
+ queryKey: ['notifications'],
+ queryFn: async () => {
+ if (!agent) throw new Error('Not authenticated');
+
+ const response = await agent.api.app.bsky.notification.listNotifications();
+ return response.data.notifications;
+ },
+ enabled: !!agent,
+ });
+}
diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts
index 7a02ddf..3a1f8a8 100644
--- a/src/routeTree.gen.ts
+++ b/src/routeTree.gen.ts
@@ -13,6 +13,7 @@ import { createFileRoute } from '@tanstack/react-router'
// Import Routes
import { Route as rootRoute } from './routes/__root'
+import { Route as NotificationsImport } from './routes/notifications'
import { Route as ProfileHandleRouteImport } from './routes/profile/$handle/route'
import { Route as ProfileHandleIndexImport } from './routes/profile/$handle/index'
import { Route as ProfileHandlePostPostIdImport } from './routes/profile/$handle/post.$postId'
@@ -38,6 +39,12 @@ const LoginLazyRoute = LoginLazyImport.update({
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/login.lazy').then((d) => d.Route))
+const NotificationsRoute = NotificationsImport.update({
+ id: '/notifications',
+ path: '/notifications',
+ getParentRoute: () => rootRoute,
+} as any)
+
const IndexLazyRoute = IndexLazyImport.update({
id: '/',
path: '/',
@@ -79,6 +86,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexLazyImport
parentRoute: typeof rootRoute
}
+ '/notifications': {
+ id: '/notifications'
+ path: '/notifications'
+ fullPath: '/notifications'
+ preLoaderRoute: typeof NotificationsImport
+ parentRoute: typeof rootRoute
+ }
'/login': {
id: '/login'
path: '/login'
@@ -141,6 +155,7 @@ const ProfileHandleRouteRouteWithChildren =
export interface FileRoutesByFullPath {
'/': typeof IndexLazyRoute
+ '/notifications': typeof NotificationsRoute
'/login': typeof LoginLazyRoute
'/settings': typeof SettingsLazyRoute
'/profile/$handle': typeof ProfileHandleRouteRouteWithChildren
@@ -151,6 +166,7 @@ export interface FileRoutesByFullPath {
export interface FileRoutesByTo {
'/': typeof IndexLazyRoute
+ '/notifications': typeof NotificationsRoute
'/login': typeof LoginLazyRoute
'/settings': typeof SettingsLazyRoute
'/tag/$tag': typeof TagTagLazyRoute
@@ -161,6 +177,7 @@ export interface FileRoutesByTo {
export interface FileRoutesById {
__root__: typeof rootRoute
'/': typeof IndexLazyRoute
+ '/notifications': typeof NotificationsRoute
'/login': typeof LoginLazyRoute
'/settings': typeof SettingsLazyRoute
'/profile/$handle': typeof ProfileHandleRouteRouteWithChildren
@@ -173,6 +190,7 @@ export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
+ | '/notifications'
| '/login'
| '/settings'
| '/profile/$handle'
@@ -182,6 +200,7 @@ export interface FileRouteTypes {
fileRoutesByTo: FileRoutesByTo
to:
| '/'
+ | '/notifications'
| '/login'
| '/settings'
| '/tag/$tag'
@@ -190,6 +209,7 @@ export interface FileRouteTypes {
id:
| '__root__'
| '/'
+ | '/notifications'
| '/login'
| '/settings'
| '/profile/$handle'
@@ -201,6 +221,7 @@ export interface FileRouteTypes {
export interface RootRouteChildren {
IndexLazyRoute: typeof IndexLazyRoute
+ NotificationsRoute: typeof NotificationsRoute
LoginLazyRoute: typeof LoginLazyRoute
SettingsLazyRoute: typeof SettingsLazyRoute
ProfileHandleRouteRoute: typeof ProfileHandleRouteRouteWithChildren
@@ -209,6 +230,7 @@ export interface RootRouteChildren {
const rootRouteChildren: RootRouteChildren = {
IndexLazyRoute: IndexLazyRoute,
+ NotificationsRoute: NotificationsRoute,
LoginLazyRoute: LoginLazyRoute,
SettingsLazyRoute: SettingsLazyRoute,
ProfileHandleRouteRoute: ProfileHandleRouteRouteWithChildren,
@@ -226,6 +248,7 @@ export const routeTree = rootRoute
"filePath": "__root.tsx",
"children": [
"/",
+ "/notifications",
"/login",
"/settings",
"/profile/$handle",
@@ -235,6 +258,9 @@ export const routeTree = rootRoute
"/": {
"filePath": "index.lazy.tsx"
},
+ "/notifications": {
+ "filePath": "notifications.tsx"
+ },
"/login": {
"filePath": "login.lazy.tsx"
},
diff --git a/src/routes/notifications.tsx b/src/routes/notifications.tsx
new file mode 100644
index 0000000..127d081
--- /dev/null
+++ b/src/routes/notifications.tsx
@@ -0,0 +1,112 @@
+import { createFileRoute } from '@tanstack/react-router';
+import { useTranslation } from 'react-i18next';
+import { useNotifications } from '../lib/bluesky/hooks/useNotifications';
+import { Debug } from '../components/ui/Debug';
+import { Notification as BskyNotification } from '@atproto/api/dist/client/types/app/bsky/notification/listNotifications';
+
+export const Route = createFileRoute('/notifications')({
+ component: RouteComponent,
+});
+
+function RouteComponent() {
+ const { t } = useTranslation(['app', 'notifications']);
+ const { data: notifications, isLoading } = useNotifications();
+
+ if (isLoading) return {t('loading')}
;
+
+ return (
+
+ {t('notifications:notifications')}
+ {notifications?.map((notification) => )}
+
+ );
+}
+
+function Notification({ notification }: { notification: BskyNotification }) {
+ switch (notification.reason) {
+ case 'follow':
+ return ;
+ case 'like':
+ return ;
+ case 'repost':
+ return ;
+ case 'reply':
+ return ;
+ case 'mention':
+ return ;
+ case 'quote':
+ return ;
+ case 'starterpack-joined':
+ return ;
+ }
+
+ return (
+
+ {notification.author.displayName}
+
+
+ );
+}
+
+function FollowNotification({ notification }: { notification: BskyNotification }) {
+ const { t } = useTranslation('notifications');
+ return (
+
+ {notification.author.displayName} {t('followedYou')}
+
+ );
+}
+
+function LikeNotification({ notification }: { notification: BskyNotification }) {
+ const { t } = useTranslation('notifications');
+ return (
+
+ {notification.author.displayName} {t('likedYourPost')}
+
+ );
+}
+
+function RepostNotification({ notification }: { notification: BskyNotification }) {
+ const { t } = useTranslation('notifications');
+ return (
+
+ {notification.author.displayName} {t('repostedYourPost')}
+
+ );
+}
+
+function ReplyNotification({ notification }: { notification: BskyNotification }) {
+ const { t } = useTranslation('notifications');
+ return (
+
+ {notification.author.displayName} {t('repliedToYourPost')}
+
+ );
+}
+
+function MentionNotification({ notification }: { notification: BskyNotification }) {
+ const { t } = useTranslation('notifications');
+ return (
+
+ {notification.author.displayName} {t('mentionedYou')}
+
+ );
+}
+
+function QuoteNotification({ notification }: { notification: BskyNotification }) {
+ const { t } = useTranslation('notifications');
+ return (
+
+ {notification.author.displayName} {t('quotedYourPost')}
+
+ );
+}
+
+function StarterpackJoinedNotification({ notification }: { notification: BskyNotification }) {
+ const { t } = useTranslation('notifications');
+ return (
+
+ {notification.author.displayName} {t('joinedYourStarterpack')}
+
+ );
+}