Skip to content

Commit a82a90b

Browse files
authored
add "Daily" frequency for email notifications (#1925)
* add "Daily" frequency for email notifications * add tests for daily digest email * convert default email digest frequecy to weekly * fix tests * remove stale console.log * remove stale import
1 parent 65e51d6 commit a82a90b

File tree

12 files changed

+74
-36
lines changed

12 files changed

+74
-36
lines changed

components/EditProfilePage/EditProfilePage.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Router from "next/router"
33
import { useState } from "react"
44
import { TabPane } from "react-bootstrap"
55
import TabContainer from "react-bootstrap/TabContainer"
6-
import { useAuth } from "../auth"
6+
import { Frequency, useAuth } from "../auth"
77
import { Container, Row, Spinner } from "../bootstrap"
88
import {
99
Profile,
@@ -87,16 +87,16 @@ export function EditProfileForm({
8787

8888
const [formUpdated, setFormUpdated] = useState(false)
8989
const [settingsModal, setSettingsModal] = useState<"show" | null>(null)
90-
const [notifications, setNotifications] = useState<
91-
"Weekly" | "Monthly" | "None"
92-
>(notificationFrequency ? notificationFrequency : "Monthly")
90+
const [notifications, setNotifications] = useState<Frequency>(
91+
notificationFrequency || "Weekly"
92+
)
9393
const [isProfilePublic, setIsProfilePublic] = useState<false | true>(
9494
isPublic ? isPublic : false
9595
)
9696

9797
const onSettingsModalOpen = () => {
9898
setSettingsModal("show")
99-
setNotifications(notificationFrequency ? notificationFrequency : "Monthly")
99+
setNotifications(notificationFrequency || "Weekly")
100100
setIsProfilePublic(isPublic ? isPublic : false)
101101
}
102102

components/EditProfilePage/ProfileSettingsModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type Props = Pick<ModalProps, "show" | "onHide"> & {
1515
role: Role
1616
setIsProfilePublic: Dispatch<SetStateAction<false | true>>
1717
notifications: Frequency
18-
setNotifications: Dispatch<SetStateAction<"Weekly" | "Monthly" | "None">>
18+
setNotifications: Dispatch<SetStateAction<Frequency>>
1919
onSettingsModalClose: () => void
2020
}
2121

components/Newsfeed/Newsfeed.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import ErrorPage from "next/error"
22
import { Timestamp } from "firebase/firestore"
33
import { useTranslation } from "next-i18next"
44
import { useEffect, useState } from "react"
5-
import { useAuth } from "../auth"
5+
import { Frequency, useAuth } from "../auth"
66
import { Col, Row, Spinner } from "../bootstrap"
77
import { Profile, useProfile, usePublicProfile } from "../db"
88
import { NotificationProps, Notifications } from "./NotificationProps"
@@ -142,9 +142,9 @@ export default function Newsfeed() {
142142
}: Profile = profile
143143

144144
const [settingsModal, setSettingsModal] = useState<"show" | null>(null)
145-
const [notifications, setNotifications] = useState<
146-
"Weekly" | "Monthly" | "None"
147-
>(notificationFrequency ? notificationFrequency : "Weekly")
145+
const [notifications, setNotifications] = useState<Frequency>(
146+
notificationFrequency ? notificationFrequency : "Weekly"
147+
)
148148
const [isProfilePublic, setIsProfilePublic] = useState<false | true>(
149149
isPublic ? isPublic : false
150150
)

components/ProfilePage/ProfileHeader.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useTranslation } from "next-i18next"
22
import { useContext, useState } from "react"
33
import { useMediaQuery } from "usehooks-ts"
4-
import { useAuth } from "../auth"
4+
import { Frequency, useAuth } from "../auth"
55
import { Profile, useProfile } from "../db"
66
import { EditProfileButton, ProfileButtons } from "./ProfileButtons"
77
import { Header, ProfileDisplayName } from "./StyledProfileComponents"
@@ -30,16 +30,16 @@ export const ProfileHeader = ({
3030
}: Profile = profile
3131

3232
const [settingsModal, setSettingsModal] = useState<"show" | null>(null)
33-
const [notifications, setNotifications] = useState<
34-
"Weekly" | "Monthly" | "None"
35-
>(notificationFrequency ? notificationFrequency : "Monthly")
33+
const [notifications, setNotifications] = useState<Frequency>(
34+
notificationFrequency || "Weekly"
35+
)
3636
const [isProfilePublic, setIsProfilePublic] = useState<false | true>(
3737
isPublic ? isPublic : false
3838
)
3939

4040
const onSettingsModalOpen = () => {
4141
setSettingsModal("show")
42-
setNotifications(notificationFrequency ? notificationFrequency : "Monthly")
42+
setNotifications(notificationFrequency || "Weekly")
4343
setIsProfilePublic(isPublic ? isPublic : false)
4444
}
4545

components/auth/hooks.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe.skip("useCreateUserWithEmailAndPassword", () => {
2626
fullName: info.fullName,
2727
role: "user",
2828
public: false,
29-
notificationFrequency: "Monthly",
29+
notificationFrequency: "Weekly",
3030
email: info.email
3131
})
3232
})

functions/src/auth/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const Claim = Record({
2424
})
2525
export type Claim = Static<typeof Claim>
2626

27-
export const Frequency = Union(L("Weekly"), L("Monthly"), L("None"))
27+
export const Frequency = Union(L("Weekly"), L("Monthly"), L("Daily"), L("None"))
2828
export type Frequency = Static<typeof Frequency>
2929

3030
export const OrgCategories = [

functions/src/email/helpers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Frequency } from "../auth/types"
12
import { BillDigest } from "../notifications/emailTypes"
23

34
export function addCounts() {
@@ -35,7 +36,7 @@ export function minusFour(value: number) {
3536
return result
3637
}
3738

38-
export function noUpdatesFormat(aString: string) {
39+
export function noUpdatesFormat(aString: Frequency) {
3940
let result = ""
4041
switch (aString) {
4142
case "Monthly":

functions/src/notifications/deliverNotifications.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,6 @@ const renderToHtmlString = (digestData: NotificationEmailDigest) => {
275275

276276
// Firebase Functions
277277
export const deliverNotifications = functions.pubsub
278-
.schedule("47 9 1 * 2") // 9:47 AM on the first day of the month and on Tuesdays
278+
.schedule("47 9 * * *") // 9:47 AM every day
279279
.timeZone("America/New_York")
280280
.onRun(deliverEmailNotifications)

functions/src/notifications/helpers.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,18 @@ describe("getNextDigestAt", () => {
7979
)
8080
consoleSpy.mockRestore()
8181
})
82+
83+
it("should return the day after for daily frequency", () => {
84+
// Set a fixed date: March 15, 2025
85+
const fixedDate = new Date(2025, 2, 15)
86+
// Next day.
87+
const expectedDate = new Date(2025, 2, 16)
88+
89+
jest.setSystemTime(fixedDate)
90+
91+
const result = getNextDigestAt("Daily")
92+
expect(result).toEqual(Timestamp.fromDate(expectedDate))
93+
})
8294
})
8395

8496
describe("getNotificationStartDate", () => {
@@ -97,4 +109,12 @@ describe("getNotificationStartDate", () => {
97109
const result = getNotificationStartDate("Monthly", fixedDate)
98110
expect(result).toEqual(expectedDate)
99111
})
112+
113+
it("should return the previous day for daily frequency", () => {
114+
const fixedDate = Timestamp.fromDate(new Date(2025, 2, 1))
115+
const expectedDate = Timestamp.fromDate(new Date(2025, 1, 28))
116+
117+
const result = getNotificationStartDate("Daily", fixedDate)
118+
expect(result).toEqual(expectedDate)
119+
})
100120
})

functions/src/notifications/helpers.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
setDate,
77
previousTuesday,
88
nextTuesday,
9-
subMonths
9+
subMonths,
10+
addDays
1011
} from "date-fns"
1112

1213
import { JSDOM } from "jsdom"
@@ -23,6 +24,9 @@ export const getNextDigestAt = (notificationFrequency: Frequency) => {
2324
const nextMonthFirst = setDate(addMonths(now, 1), 1)
2425
nextDigestAt = Timestamp.fromDate(nextMonthFirst)
2526
break
27+
case "Daily":
28+
nextDigestAt = Timestamp.fromDate(addDays(now, 1))
29+
break
2630
case "None":
2731
nextDigestAt = null
2832
break
@@ -45,6 +49,8 @@ export const getNotificationStartDate = (
4549
const firstOfMonth = setDate(now.toDate(), 1)
4650
const previousFirstOfMonth = subMonths(firstOfMonth, 1)
4751
return Timestamp.fromDate(previousFirstOfMonth)
52+
case "Daily":
53+
return Timestamp.fromDate(addDays(now.toDate(), -1))
4854
// We can safely fallthrough here because if the user has no notification frequency set,
4955
// we won't even send a notification
5056
default:

0 commit comments

Comments
 (0)