Skip to content

Commit

Permalink
feat: add dialog to setup notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
czabaj committed Dec 21, 2024
1 parent b325f58 commit b2c422a
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 26 deletions.
13 changes: 13 additions & 0 deletions src/backend/Db.res
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,19 @@ module Place = {
let placeDoc = placeDocument(firestore, placeId)
Firebase.updateDoc(placeDoc, {"createdAt": createdAt, "name": name})
}
let updateNotificationSubscription = async (
firestore,
~personUserId,
~newSubscription: int,
~placeId,
) => {
let placeRef = placeDocument(firestore, placeId)
let place = (await Firebase.getDocFromCache(placeRef)).data({})
let (role, _) = place.accounts->Dict.getUnsafe(personUserId)
let updateObject = Object.make()
updateObject->Object.set(`accounts.${personUserId}`, (role, newSubscription))
await Firebase.updateDoc(placeRef, updateObject)
}
}

module Person = {
Expand Down
23 changes: 14 additions & 9 deletions src/backend/NotificationHooks.res
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
let isMobileIOs: bool = %raw(`navigator.userAgent.match(/(iPhone|iPad)/)`)
let canSubscribe =
// on iOS the notifications are only allowed in standalone mode
!isMobileIOs || %raw(`window.navigator.standalone === true`)

@genType
type notificationEventMessages =
| @as(1) /* FreeTable */ FreeTableMessage({place: string, users: array<string>})
Expand Down Expand Up @@ -64,15 +69,15 @@ let useDispatchFreshKegNotification = (~currentUserUid: string, ~place: Firestor
}
}

// let useDispatchTestNotification = (~currentUserUid: string, ~place: FirestoreModels.place) => {
// let firestore = Reactfire.useFirestore()
// let functions = Reactfire.useFunctions()
// let dispatchNotification = Firebase.Functions.httpsCallable(functions, "dispatchNotification")
// () => {
// let placeRef = Db.placeDocument(firestore, Db.getUid(place))
// dispatchNotification(FreeTableMessage({place: placeRef.path, users: [currentUserUid]}))->ignore
// }
// }
let useDispatchTestNotification = (~currentUserUid: string, ~place: FirestoreModels.place) => {
let firestore = Reactfire.useFirestore()
let functions = Reactfire.useFunctions()
let dispatchNotification = Firebase.Functions.httpsCallable(functions, "dispatchNotification")
() => {
let placeRef = Db.placeDocument(firestore, Db.getUid(place))
dispatchNotification(FreeTableMessage({place: placeRef.path, users: [currentUserUid]}))->ignore
}
}

let useUpdateNotificationToken = () => {
let functions = Reactfire.useFunctions()
Expand Down
10 changes: 2 additions & 8 deletions src/components/FcmTokenSync/FcmTokenSync.res
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
let isMobileIOs: bool = %raw(`navigator.userAgent.match(/(iPhone|iPad)/)`)
let canSubscribe =
// avoid excessive call of the cloud function in development
%raw(`import.meta.env.PROD`) &&
(// on iOS the notifications are only allowed in standalone mode
!isMobileIOs || %raw(`window.navigator.standalone === true`))

let isSubscribedToNotificationsRx = (auth, firestore, placeId) => {
open Rxjs
let currentUserRx = Rxfire.user(auth)->op(keepMap(Null.toOption))
Expand All @@ -26,7 +19,8 @@ let isSubscribedToNotificationsRx = (auth, firestore, placeId) => {

@react.component
let make = React.memo((~placeId) => {
if canSubscribe {
// avoid excessive call of the cloud function in development
if %raw(`import.meta.env.PROD`) && NotificationHooks.canSubscribe {
let auth = Reactfire.useAuth()
let firestore = Reactfire.useFirestore()
let messaging = Reactfire.useMessaging()
Expand Down
5 changes: 5 additions & 0 deletions src/pages/Place/NotificationDialog.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.notificationDialog {
& fieldset {
grid-template-columns: 1fr auto;
}
}
84 changes: 84 additions & 0 deletions src/pages/Place/NotificationDialog.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
type classesType = {notificationDialog: string}

@module("./NotificationDialog.module.css") external classes: classesType = "default"

@react.component
let make = (
~currentUserNotificationSubscription,
~currentUserUid,
~onDismiss,
~onUpdateSubscription,
~place,
) => {
let sendTestNotification = NotificationHooks.useDispatchTestNotification(~currentUserUid, ~place)
<Dialog className={classes.notificationDialog} visible={true}>
<header>
<h3> {React.string(`Nastavení notifikací`)} </h3>
<p> {React.string(`Přihlaš se k odběru důležitých zpráv.`)} </p>
</header>
<form>
<fieldset className={`reset ${Styles.fieldset.grid}`}>
<InputWrapper
inputName="free_table"
inputSlot={<InputToggle
checked={BitwiseUtils.bitAnd(
currentUserNotificationSubscription,
(NotificationEvents.FreeTable :> int),
) !== 0}
onChange={event => {
let target = event->ReactEvent.Form.target
let checked = target["checked"]
onUpdateSubscription(
checked
? BitwiseUtils.bitOr(
currentUserNotificationSubscription,
(NotificationEvents.FreeTable :> int),
)
: BitwiseUtils.bitAnd(
currentUserNotificationSubscription,
BitwiseUtils.bitNot((NotificationEvents.FreeTable :> int)),
),
)
}}
/>}
labelSlot={React.string("První pivo")}
/>
<InputWrapper
inputName="fresh_keg"
inputSlot={<InputToggle
checked={BitwiseUtils.bitAnd(
currentUserNotificationSubscription,
(NotificationEvents.FreshKeg :> int),
) !== 0}
onChange={event => {
let target = event->ReactEvent.Form.target
let checked = target["checked"]
onUpdateSubscription(
checked
? BitwiseUtils.bitOr(
currentUserNotificationSubscription,
(NotificationEvents.FreshKeg :> int),
)
: BitwiseUtils.bitAnd(
currentUserNotificationSubscription,
BitwiseUtils.bitNot((NotificationEvents.FreshKeg :> int)),
),
)
}}
/>}
labelSlot={React.string("Čerstvý sud")}
/>
</fieldset>
</form>
{%raw(`import.meta.env.PROD`)
? React.null
: <button className={Styles.button.base} type_="button" onClick={_ => sendTestNotification()}>
{React.string("Send test notification")}
</button>}
<footer>
<button className={Styles.button.base} type_="button" onClick={_ => onDismiss()}>
{React.string("Zavřít")}
</button>
</footer>
</Dialog>
}
11 changes: 11 additions & 0 deletions src/pages/Place/Place.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@
align-self: center;
}

.toolbar {
display: flex;
gap: 0.5rem;
grid-area: more;
align-items: center;

& > button:first-child {
gap: 0.25rem;
}
}

li:first-child .detailButton {
border-start-end-radius: var(--radius-2);
}
Expand Down
46 changes: 37 additions & 9 deletions src/pages/Place/Place.res
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
type classesType = {root: string}
type classesType = {root: string, toolbar: string}

@module("./Place.module.css") external classes: classesType = "default"

type dialogState =
| Hidden
| AddConsumption({personId: string, person: Db.personsAllRecord})
| AddPerson
| NotificationSettings

type userConsumption = {milliliters: int, timestamp: float}

Expand Down Expand Up @@ -136,14 +137,25 @@ let make = (~placeId) => {
<FormattedCurrency.Provider value={place.currency}>
<div className={`${Styles.page.narrow} ${classes.root}`}>
<PlaceHeader
buttonRightSlot={isUserAuthorized(UserRoles.Staff)
? <a
{...RouterUtils.createAnchorProps("./nastaveni")}
className={Header.classes.buttonRight}>
<span> {React.string("⚙️")} </span>
<span> {React.string("Nastavení")} </span>
</a>
: React.null}
buttonRightSlot={<div className={classes.toolbar}>
{!NotificationHooks.canSubscribe
? React.null
: <button
className={Header.classes.buttonRight}
onClick={_ => setDialog(_ => NotificationSettings)}
type_="button">
<span> {React.string("📢")} </span>
<span> {React.string("Notifikace")} </span>
</button>}
{isUserAuthorized(UserRoles.Staff)
? <a
{...RouterUtils.createAnchorProps("./nastaveni")}
className={Header.classes.buttonRight}>
<span> {React.string("⚙️")} </span>
<span> {React.string("Nastavení")} </span>
</a>
: React.null}
</div>}
createdTimestamp={place.createdAt}
placeName={place.name}
/>
Expand Down Expand Up @@ -237,6 +249,22 @@ let make = (~placeId) => {
hideDialog()
}}
/>
| NotificationSettings =>
let currentUserNotificationSubscription =
place.accounts->Dict.get(currentUser.uid)->Option.getExn->snd
<NotificationDialog
currentUserNotificationSubscription
currentUserUid={currentUser.uid}
onDismiss={hideDialog}
onUpdateSubscription={newNotificationSubscription =>
Db.Place.updateNotificationSubscription(
firestore,
~placeId,
~personUserId=currentUser.uid,
~newSubscription=newNotificationSubscription,
)->ignore}
place={place}
/>
}}
</div>
</FormattedCurrency.Provider>
Expand Down
2 changes: 2 additions & 0 deletions src/utils/BitwiseUtils.res
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
let bitAnd = (a: int, b: int): int => %raw(`a & b`)

let bitOr = (a: int, b: int): int => %raw(`a | b`)

let bitNot = (a: int): int => %raw(`~a`)

0 comments on commit b2c422a

Please sign in to comment.