-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathuse-register-notifications.ts
More file actions
162 lines (144 loc) · 4.85 KB
/
Copy pathuse-register-notifications.ts
File metadata and controls
162 lines (144 loc) · 4.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import { useEffect, useRef } from "react"
import { useAccount } from "jazz-tools/react"
import { Group, type co, type ResolveQuery } from "jazz-tools"
import { PUBLIC_JAZZ_WORKER_ACCOUNT } from "astro:env/client"
import { UserAccount } from "#shared/schema/user"
import { apiClient } from "#app/lib/api-client"
import { tryCatch } from "#shared/lib/trycatch"
import {
migrateNotificationSettings,
addServerToGroup,
} from "#app/lib/notification-settings-migration"
import { findLatestFutureDate } from "#app/lib/reminder-utils"
export { useRegisterNotifications }
let notificationSettingsQuery = {
root: {
notificationSettings: true,
people: { $each: { reminders: { $each: true } } },
},
} as const satisfies ResolveQuery<typeof UserAccount>
type LoadedAccount = co.loaded<
typeof UserAccount,
typeof notificationSettingsQuery
>
/**
* Hook that registers notification settings with the server.
* Handles migration from account-owned to group-owned settings.
* Runs once on app start.
*/
function useRegisterNotifications(): void {
let registrationRan = useRef(false)
let me = useAccount(UserAccount, { resolve: notificationSettingsQuery })
useEffect(() => {
if (registrationRan.current || !me.$isLoaded) return
if (!me.root.notificationSettings) return
registrationRan.current = true
registerNotificationSettings(me).catch(error => {
console.error("[Notifications] Registration error:", error)
registrationRan.current = false
})
}, [me.$isLoaded, me])
}
async function registerNotificationSettings(me: LoadedAccount): Promise<void> {
let notificationSettings = me.root.notificationSettings
if (!notificationSettings) return
let serverAccountId = PUBLIC_JAZZ_WORKER_ACCOUNT
if (!serverAccountId) {
console.error("[Notifications] PUBLIC_JAZZ_WORKER_ACCOUNT not configured")
return
}
// Sync language from root to notification settings
let rootLanguage = me.root.language
if (rootLanguage && notificationSettings.language !== rootLanguage) {
notificationSettings.$jazz.set("language", rootLanguage)
}
// Compute and sync latestReminderDueDate
let latestDueDate = computeLatestReminderDueDate(me)
if (latestDueDate !== notificationSettings.latestReminderDueDate) {
notificationSettings.$jazz.set("latestReminderDueDate", latestDueDate)
}
// Check if settings are owned by a shareable group
// The key difference: if owner is an Account vs a Group
let owner = notificationSettings.$jazz.owner
let isShareableGroup = owner instanceof Group
if (!isShareableGroup) {
console.log("[Notifications] Migrating to shareable group")
let migrationResult = await tryCatch(
migrateNotificationSettings(notificationSettings, serverAccountId, {
loadAs: me,
rootLanguage,
}),
)
if (!migrationResult.ok) {
console.error("[Notifications] Migration failed:", migrationResult.error)
return
}
// Update root to point to new settings
me.root.$jazz.set("notificationSettings", migrationResult.data)
notificationSettings = migrationResult.data
console.log("[Notifications] Migration complete")
} else {
// Ensure server worker is a member
let group = owner as Group
let serverIsMember = group.members.some(
m => m.account?.$jazz.id === serverAccountId,
)
if (!serverIsMember) {
let addResult = await tryCatch(
addServerToGroup(group, serverAccountId, { loadAs: me }),
)
if (!addResult.ok) {
console.error(
"[Notifications] Failed to add server to group:",
addResult.error,
)
}
}
}
// Register with server
let registerResult = await tryCatch(
apiClient.push.register.$post({
json: { notificationSettingsId: notificationSettings.$jazz.id },
}),
)
if (!registerResult.ok) {
console.error("[Notifications] Registration failed:", registerResult.error)
return
}
if (!registerResult.data.ok) {
let errorData = await tryCatch(registerResult.data.json())
console.error(
"[Notifications] Registration error:",
errorData.ok ? errorData.data : "Unknown error",
)
return
}
console.log("[Notifications] Registration successful")
}
function computeLatestReminderDueDate(me: LoadedAccount): string | undefined {
let reminders = extractReminders(me)
let timezone =
me.root.notificationSettings?.timezone ||
Intl.DateTimeFormat().resolvedOptions().timeZone
let today = new Date()
.toLocaleDateString("sv-SE", { timeZone: timezone })
.slice(0, 10)
return findLatestFutureDate(reminders, today)
}
function extractReminders(
me: LoadedAccount,
): { dueAtDate: string; deleted: boolean; done: boolean }[] {
let reminders: { dueAtDate: string; deleted: boolean; done: boolean }[] = []
for (let person of me.root.people.values()) {
if (!person || person.deletedAt) continue
for (let reminder of person.reminders.values()) {
if (!reminder) continue
reminders.push({
dueAtDate: reminder.dueAtDate,
deleted: !!reminder.deletedAt,
done: !!reminder.done,
})
}
}
return reminders
}