Skip to content

Commit b714e8c

Browse files
authored
feat: basic notifications (#9)
1 parent 49d1fd3 commit b714e8c

File tree

5 files changed

+173
-1
lines changed

5 files changed

+173
-1
lines changed

src/components/Navbar.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import { useAuth } from '../lib/bluesky/hooks/useAuth';
33
import { Link } from './ui/Link';
44
import { useBlueskyStore } from '../lib/bluesky/store';
55

6+
const NotificationsLink = () => {
7+
const { t } = useTranslation('notifications');
8+
return <Link to="/notifications">{t('notifications')}</Link>;
9+
};
10+
611
const ProfileLink = () => {
712
const { session } = useBlueskyStore();
813
const { t } = useTranslation('profile');
@@ -48,7 +53,8 @@ export const Navbar = () => {
4853
<h1 className="text-2xl font-bold">{t('appName')}</h1>
4954
</Link>
5055
<div className="flex flex-row gap-2">
51-
<ProfileLink />
56+
{isAuthenticated && <NotificationsLink />}
57+
{isAuthenticated && <ProfileLink />}
5258
<SettingsLink />
5359
{isAuthenticated ? <LogoutButton /> : <LoginButton />}
5460
</div>

src/i18n/lang/en.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,15 @@ export const en = {
8181
debug: {
8282
notImplemented: 'NOT IMPLEMENTED: {{value}}',
8383
},
84+
notifications: {
85+
notifications: 'notifications',
86+
noNotifications: 'no notifications',
87+
followedYou: 'followed you',
88+
likedYourPost: 'liked your post',
89+
repostedYourPost: 'reposted your post',
90+
repliedToYourPost: 'replied to your post',
91+
mentionedYou: 'mentioned you',
92+
quotedYourPost: 'quoted your post',
93+
joinedYourStarterpack: 'joined your starterpack',
94+
},
8495
} as const;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useQuery } from '@tanstack/react-query';
2+
import { useBlueskyStore } from '../store';
3+
4+
export function useNotifications() {
5+
const { agent } = useBlueskyStore();
6+
7+
return useQuery({
8+
queryKey: ['notifications'],
9+
queryFn: async () => {
10+
if (!agent) throw new Error('Not authenticated');
11+
12+
const response = await agent.api.app.bsky.notification.listNotifications();
13+
return response.data.notifications;
14+
},
15+
enabled: !!agent,
16+
});
17+
}

src/routeTree.gen.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { createFileRoute } from '@tanstack/react-router'
1313
// Import Routes
1414

1515
import { Route as rootRoute } from './routes/__root'
16+
import { Route as NotificationsImport } from './routes/notifications'
1617
import { Route as ProfileHandleRouteImport } from './routes/profile/$handle/route'
1718
import { Route as ProfileHandleIndexImport } from './routes/profile/$handle/index'
1819
import { Route as ProfileHandlePostPostIdImport } from './routes/profile/$handle/post.$postId'
@@ -38,6 +39,12 @@ const LoginLazyRoute = LoginLazyImport.update({
3839
getParentRoute: () => rootRoute,
3940
} as any).lazy(() => import('./routes/login.lazy').then((d) => d.Route))
4041

42+
const NotificationsRoute = NotificationsImport.update({
43+
id: '/notifications',
44+
path: '/notifications',
45+
getParentRoute: () => rootRoute,
46+
} as any)
47+
4148
const IndexLazyRoute = IndexLazyImport.update({
4249
id: '/',
4350
path: '/',
@@ -79,6 +86,13 @@ declare module '@tanstack/react-router' {
7986
preLoaderRoute: typeof IndexLazyImport
8087
parentRoute: typeof rootRoute
8188
}
89+
'/notifications': {
90+
id: '/notifications'
91+
path: '/notifications'
92+
fullPath: '/notifications'
93+
preLoaderRoute: typeof NotificationsImport
94+
parentRoute: typeof rootRoute
95+
}
8296
'/login': {
8397
id: '/login'
8498
path: '/login'
@@ -141,6 +155,7 @@ const ProfileHandleRouteRouteWithChildren =
141155

142156
export interface FileRoutesByFullPath {
143157
'/': typeof IndexLazyRoute
158+
'/notifications': typeof NotificationsRoute
144159
'/login': typeof LoginLazyRoute
145160
'/settings': typeof SettingsLazyRoute
146161
'/profile/$handle': typeof ProfileHandleRouteRouteWithChildren
@@ -151,6 +166,7 @@ export interface FileRoutesByFullPath {
151166

152167
export interface FileRoutesByTo {
153168
'/': typeof IndexLazyRoute
169+
'/notifications': typeof NotificationsRoute
154170
'/login': typeof LoginLazyRoute
155171
'/settings': typeof SettingsLazyRoute
156172
'/tag/$tag': typeof TagTagLazyRoute
@@ -161,6 +177,7 @@ export interface FileRoutesByTo {
161177
export interface FileRoutesById {
162178
__root__: typeof rootRoute
163179
'/': typeof IndexLazyRoute
180+
'/notifications': typeof NotificationsRoute
164181
'/login': typeof LoginLazyRoute
165182
'/settings': typeof SettingsLazyRoute
166183
'/profile/$handle': typeof ProfileHandleRouteRouteWithChildren
@@ -173,6 +190,7 @@ export interface FileRouteTypes {
173190
fileRoutesByFullPath: FileRoutesByFullPath
174191
fullPaths:
175192
| '/'
193+
| '/notifications'
176194
| '/login'
177195
| '/settings'
178196
| '/profile/$handle'
@@ -182,6 +200,7 @@ export interface FileRouteTypes {
182200
fileRoutesByTo: FileRoutesByTo
183201
to:
184202
| '/'
203+
| '/notifications'
185204
| '/login'
186205
| '/settings'
187206
| '/tag/$tag'
@@ -190,6 +209,7 @@ export interface FileRouteTypes {
190209
id:
191210
| '__root__'
192211
| '/'
212+
| '/notifications'
193213
| '/login'
194214
| '/settings'
195215
| '/profile/$handle'
@@ -201,6 +221,7 @@ export interface FileRouteTypes {
201221

202222
export interface RootRouteChildren {
203223
IndexLazyRoute: typeof IndexLazyRoute
224+
NotificationsRoute: typeof NotificationsRoute
204225
LoginLazyRoute: typeof LoginLazyRoute
205226
SettingsLazyRoute: typeof SettingsLazyRoute
206227
ProfileHandleRouteRoute: typeof ProfileHandleRouteRouteWithChildren
@@ -209,6 +230,7 @@ export interface RootRouteChildren {
209230

210231
const rootRouteChildren: RootRouteChildren = {
211232
IndexLazyRoute: IndexLazyRoute,
233+
NotificationsRoute: NotificationsRoute,
212234
LoginLazyRoute: LoginLazyRoute,
213235
SettingsLazyRoute: SettingsLazyRoute,
214236
ProfileHandleRouteRoute: ProfileHandleRouteRouteWithChildren,
@@ -226,6 +248,7 @@ export const routeTree = rootRoute
226248
"filePath": "__root.tsx",
227249
"children": [
228250
"/",
251+
"/notifications",
229252
"/login",
230253
"/settings",
231254
"/profile/$handle",
@@ -235,6 +258,9 @@ export const routeTree = rootRoute
235258
"/": {
236259
"filePath": "index.lazy.tsx"
237260
},
261+
"/notifications": {
262+
"filePath": "notifications.tsx"
263+
},
238264
"/login": {
239265
"filePath": "login.lazy.tsx"
240266
},

src/routes/notifications.tsx

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { createFileRoute } from '@tanstack/react-router';
2+
import { useTranslation } from 'react-i18next';
3+
import { useNotifications } from '../lib/bluesky/hooks/useNotifications';
4+
import { Debug } from '../components/ui/Debug';
5+
import { Notification as BskyNotification } from '@atproto/api/dist/client/types/app/bsky/notification/listNotifications';
6+
7+
export const Route = createFileRoute('/notifications')({
8+
component: RouteComponent,
9+
});
10+
11+
function RouteComponent() {
12+
const { t } = useTranslation(['app', 'notifications']);
13+
const { data: notifications, isLoading } = useNotifications();
14+
15+
if (isLoading) return <div>{t('loading')}</div>;
16+
17+
return (
18+
<div>
19+
{t('notifications:notifications')}
20+
{notifications?.map((notification) => <Notification key={notification.uri} notification={notification} />)}
21+
</div>
22+
);
23+
}
24+
25+
function Notification({ notification }: { notification: BskyNotification }) {
26+
switch (notification.reason) {
27+
case 'follow':
28+
return <FollowNotification notification={notification} />;
29+
case 'like':
30+
return <LikeNotification notification={notification} />;
31+
case 'repost':
32+
return <RepostNotification notification={notification} />;
33+
case 'reply':
34+
return <ReplyNotification notification={notification} />;
35+
case 'mention':
36+
return <MentionNotification notification={notification} />;
37+
case 'quote':
38+
return <QuoteNotification notification={notification} />;
39+
case 'starterpack-joined':
40+
return <StarterpackJoinedNotification notification={notification} />;
41+
}
42+
43+
return (
44+
<div>
45+
{notification.author.displayName}
46+
<Debug value={notification} />
47+
</div>
48+
);
49+
}
50+
51+
function FollowNotification({ notification }: { notification: BskyNotification }) {
52+
const { t } = useTranslation('notifications');
53+
return (
54+
<div>
55+
{notification.author.displayName} {t('followedYou')}
56+
</div>
57+
);
58+
}
59+
60+
function LikeNotification({ notification }: { notification: BskyNotification }) {
61+
const { t } = useTranslation('notifications');
62+
return (
63+
<div>
64+
{notification.author.displayName} {t('likedYourPost')}
65+
</div>
66+
);
67+
}
68+
69+
function RepostNotification({ notification }: { notification: BskyNotification }) {
70+
const { t } = useTranslation('notifications');
71+
return (
72+
<div>
73+
{notification.author.displayName} {t('repostedYourPost')}
74+
</div>
75+
);
76+
}
77+
78+
function ReplyNotification({ notification }: { notification: BskyNotification }) {
79+
const { t } = useTranslation('notifications');
80+
return (
81+
<div>
82+
{notification.author.displayName} {t('repliedToYourPost')}
83+
</div>
84+
);
85+
}
86+
87+
function MentionNotification({ notification }: { notification: BskyNotification }) {
88+
const { t } = useTranslation('notifications');
89+
return (
90+
<div>
91+
{notification.author.displayName} {t('mentionedYou')}
92+
</div>
93+
);
94+
}
95+
96+
function QuoteNotification({ notification }: { notification: BskyNotification }) {
97+
const { t } = useTranslation('notifications');
98+
return (
99+
<div>
100+
{notification.author.displayName} {t('quotedYourPost')}
101+
</div>
102+
);
103+
}
104+
105+
function StarterpackJoinedNotification({ notification }: { notification: BskyNotification }) {
106+
const { t } = useTranslation('notifications');
107+
return (
108+
<div>
109+
{notification.author.displayName} {t('joinedYourStarterpack')}
110+
</div>
111+
);
112+
}

0 commit comments

Comments
 (0)