Skip to content

Commit 3260be2

Browse files
authored
Update the 1.4 branch before merging Next 15 to main (#253)
* Fix backlink in activity page redirecting to groups * Hotifx 215 notification redirect (#252) * Use Typescript provided type libs for service worker * Use PushMessage type when sending as well * Properly handle push data passing and navigation * Create a dummy button for testing notifications
1 parent 94ceca4 commit 3260be2

8 files changed

Lines changed: 48 additions & 164 deletions

File tree

src/components/Expense/ExpensePage.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Image from 'next/image';
55
import { type User as NextUser } from 'next-auth';
66
import React from 'react';
77

8+
// import { api } from '~/utils/api';
89
import { toUIString } from '~/utils/numbers';
910

1011
import { UserAvatar } from '../ui/avatar';
@@ -29,6 +30,8 @@ const ExpenseDetails: React.FC<ExpenseDetailsProps> = ({ user, expense, storageP
2930

3031
const CategoryIcon = CategoryIcons[expense.category] ?? Banknote;
3132

33+
// const sendNotificationMutation = api.user.sendExpensePushNotification.useMutation();
34+
3235
return (
3336
<div className="">
3437
<div className="mb-4 flex items-start justify-between gap-2 px-6">
@@ -102,6 +105,16 @@ const ExpenseDetails: React.FC<ExpenseDetailsProps> = ({ user, expense, storageP
102105
</div>
103106
<Separator />
104107
<div className="mt-10 flex items-center gap-5 px-6">
108+
{/* <button
109+
onClick={() => {
110+
sendNotificationMutation.mutate({
111+
expenseId: expense.id,
112+
});
113+
}}
114+
className="rounded-md bg-blue-500 px-3 py-1 text-sm text-white hover:bg-blue-600"
115+
>
116+
Test Notification
117+
</button> */}
105118
<UserAvatar user={expense.paidByUser} size={35} />
106119
<p>
107120
{youPaid ? 'You' : (expense.paidByUser.name ?? expense.paidByUser.email)} paid{' '}

src/pages/activity.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,7 @@ const ActivityPage: NextPageWithUser = ({ user }) => {
6868
<div className="mt-[30vh] text-center text-gray-400">No activities yet</div>
6969
) : null}
7070
{expensesQuery.data?.map((e) => (
71-
<Link
72-
href={`${e.expense.groupId ? `/groups/${e.expense.groupId}/` : '/'}expenses/${e.expenseId}`}
73-
key={e.expenseId}
74-
className="flex gap-2"
75-
>
71+
<Link href={`/expenses/${e.expenseId}`} key={e.expenseId} className="flex gap-2">
7672
<div className="mt-1">
7773
<UserAvatar user={e.expense.paidByUser} size={30} />
7874
</div>

src/server/api/routers/user.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { getDocumentUploadUrl } from '~/server/storage';
1313
import { SplitwiseGroupSchema, SplitwiseUserSchema } from '~/types';
1414
import { BigMath } from '~/utils/numbers';
1515

16+
// import { sendExpensePushNotification } from '../services/notificationService';
1617
import {
1718
addUserExpense,
1819
deleteExpense,
@@ -390,6 +391,18 @@ export const userRouter = createTRPCRouter({
390391
return expenses;
391392
}),
392393

394+
// sendExpensePushNotification: protectedProcedure
395+
// .input(z.object({ expenseId: z.string() }))
396+
// .mutation(async ({ input }) => {
397+
// sendExpensePushNotification(input.expenseId).catch((err) => {
398+
// console.error('Error sending push notification', err);
399+
// throw new TRPCError({
400+
// code: 'INTERNAL_SERVER_ERROR',
401+
// message: 'Failed to send push notification',
402+
// });
403+
// });
404+
// }),
405+
393406
getUserDetails: protectedProcedure
394407
.input(z.object({ userId: z.number() }))
395408
.query(async ({ input }) => {

src/server/notification.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { type PushSubscription, sendNotification, setVapidDetails } from 'web-push';
22

33
import { env } from '~/env';
4+
import { type PushMessage } from '~/types';
45

56
if (env.WEB_PUSH_EMAIL && env.WEB_PUSH_PUBLIC_KEY && env.WEB_PUSH_PRIVATE_KEY) {
67
setVapidDetails(
@@ -10,10 +11,7 @@ if (env.WEB_PUSH_EMAIL && env.WEB_PUSH_PUBLIC_KEY && env.WEB_PUSH_PRIVATE_KEY) {
1011
);
1112
}
1213

13-
export async function pushNotification(
14-
subscription: string,
15-
message: { title: string; message: string; data?: { url?: string } },
16-
) {
14+
export async function pushNotification(subscription: string, message: PushMessage) {
1715
try {
1816
const _subscription = JSON.parse(subscription) as PushSubscription;
1917
const response = await sendNotification(_subscription, JSON.stringify(message));

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { z } from 'zod';
55
// eslint-disable-next-line @typescript-eslint/ban-types
66
export type NextPageWithUser<T = {}> = NextPage<{ user: User } & T> & { auth: boolean };
77

8-
export type PushMessage = { title: string; message: string };
8+
export type PushMessage = { title: string; message: string; data?: { url?: string } };
99

1010
export type SplitwisePicture = {
1111
small: string;

src/types/service-worker.d.ts

Lines changed: 0 additions & 130 deletions
This file was deleted.

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"checkJs": true,
1616

1717
/* Bundled projects */
18-
"lib": ["dom", "dom.iterable", "ES2022"],
18+
"lib": ["dom", "dom.iterable", "ES2022", "WebWorker"],
1919
"noEmit": true,
2020
"module": "ESNext",
2121
"moduleResolution": "Bundler",

worker/index.ts

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,33 @@ import { type PushMessage } from '~/types';
33
declare let self: ServiceWorkerGlobalScope;
44

55
self.addEventListener('push', function (event) {
6-
if (!event) return;
7-
const data = JSON.parse(event?.data?.text() ?? '{}') as PushMessage;
6+
const { title, message, data } = JSON.parse(event?.data?.text() ?? '{}') as PushMessage;
87
event.waitUntil(
9-
self.registration.showNotification(data.title, {
10-
body: data.message,
8+
self.registration.showNotification(title, {
9+
body: message,
1110
icon: '/icons/android-chrome-192x192.png',
11+
data,
1212
}),
1313
);
1414
});
1515

1616
self.addEventListener('notificationclick', function (event) {
17-
if (!event) return;
18-
1917
event.notification.close();
2018
event.waitUntil(
21-
self.clients
22-
.matchAll({ type: 'window', includeUncontrolled: true })
23-
.then(function (clientList) {
24-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
25-
const url = (event.notification.data?.url as string) ?? '/';
19+
(async () => {
20+
const clientList = await self.clients.matchAll({ type: 'window' });
21+
22+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
23+
const url = (event.notification.data?.url as string) ?? '/';
2624

27-
if (clientList.length > 0) {
28-
let client = clientList[0];
29-
for (const _client of clientList) {
30-
if (_client.focused) {
31-
client = _client;
32-
}
33-
}
25+
const matchingClient = clientList.find((client) => client.focused) ?? clientList[0];
3426

35-
client!.url = url;
36-
return client?.focus();
37-
}
38-
return self.clients?.openWindow(url);
39-
}),
27+
if (matchingClient) {
28+
const client = await matchingClient.focus();
29+
await client.navigate(url);
30+
} else {
31+
await self.clients.openWindow(url);
32+
}
33+
})(),
4034
);
4135
});

0 commit comments

Comments
 (0)