diff --git a/client/src/components/profile-my/notification-settings.component.jsx b/client/src/components/profile-my/notification-settings.component.jsx
index 328c85cf1..f1a2a1e73 100644
--- a/client/src/components/profile-my/notification-settings.component.jsx
+++ b/client/src/components/profile-my/notification-settings.component.jsx
@@ -1,6 +1,6 @@
import { useMutation, useQuery } from "@apollo/client";
-import React, { useEffect, useState } from "react";
-import { Button, Card, Col, Form, Row, Switch } from "antd";
+import { useEffect, useState } from "react";
+import { Button, Card, Col, Form, Row } from "antd";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -8,9 +8,34 @@ import { createStructuredSelector } from "reselect";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import { QUERY_NOTIFICATION_SETTINGS, UPDATE_NOTIFICATION_SETTINGS } from "../../graphql/user.queries.js";
-import notificationScenarios from "../../utils/jobNotificationScenarios.js";
+import { notificationScenarios, notificationChannels } from "../../utils/jobNotificationScenarios.js";
import LoadingSpinner from "../loading-spinner/loading-spinner.component.jsx";
+const NotificationMethodButtonGroup = ({ value = {}, onChange }) => {
+ const { t } = useTranslation();
+ const toggleMethod = (method) => {
+ const newValue = { ...value, [method]: !value[method] };
+ if (onChange) {
+ onChange(newValue);
+ }
+ };
+
+ return (
+
+ {notificationChannels.map((method) => (
+
+ ))}
+
+ );
+};
+
function NotificationSettingsForm({ currentUser }) {
const { t } = useTranslation();
const [form] = Form.useForm();
@@ -31,8 +56,10 @@ function NotificationSettingsForm({ currentUser }) {
useEffect(() => {
if (data?.associations?.length > 0) {
const settings = data.associations[0].notification_settings || {};
+ // For each scenario, expect an object with keys { app, email, fcm }.
+ // If not present in the fetched data, default to all false.
const formattedValues = notificationScenarios.reduce((acc, scenario) => {
- acc[scenario] = settings[scenario] ?? false;
+ acc[scenario] = settings[scenario] ?? { app: false, email: false, fcm: false };
return acc;
}, {});
@@ -45,6 +72,7 @@ function NotificationSettingsForm({ currentUser }) {
const handleSave = async (values) => {
if (data?.associations?.length > 0) {
const userId = data.associations[0].id;
+ // `values` now contains, for each scenario, an object with keys { app, email, fcm }
await updateNotificationSettings({ variables: { id: userId, ns: values } });
setInitialValues(values);
setIsDirty(false);
@@ -88,8 +116,8 @@ function NotificationSettingsForm({ currentUser }) {
{notificationScenarios.map((scenario) => (
-
-
+
+
))}
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index afb2f9b4f..f4a3a9f2b 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -3761,7 +3761,7 @@
},
"notifications": {
"labels": {
- "notificationscenarios": "Notification Scenarios",
+ "notificationscenarios": "Job Notification Scenarios",
"save": "Save Scenarios",
"watching-issue": "Watching",
"add-watchers": "Add Watchers",
@@ -3795,6 +3795,11 @@
"job-status-change": "Job Status Changed",
"payment-collected-completed": "Payment Collected / Completed",
"alternate-transport-changed": "Alternate Transport Changed"
+ },
+ "channels": {
+ "app": "App",
+ "email": "Email",
+ "fcm": "Push"
}
}
}
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index f072864c9..18fde0bbc 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -3795,6 +3795,11 @@
"job-status-change": "",
"payment-collected-completed": "",
"alternate-transport-changed": ""
+ },
+ "channels": {
+ "app": "",
+ "email": "",
+ "fcm": ""
}
}
}
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index 1fb719619..5c0b4396a 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -3795,6 +3795,11 @@
"job-status-change": "",
"payment-collected-completed": "",
"alternate-transport-changed": ""
+ },
+ "channels": {
+ "app": "",
+ "email": "",
+ "fcm": ""
}
}
}
diff --git a/client/src/utils/jobNotificationScenarios.js b/client/src/utils/jobNotificationScenarios.js
index 0ccf6d4df..1f5fddfab 100644
--- a/client/src/utils/jobNotificationScenarios.js
+++ b/client/src/utils/jobNotificationScenarios.js
@@ -16,4 +16,6 @@ const notificationScenarios = [
"alternate-transport-changed"
];
-export default notificationScenarios;
+const notificationChannels = ["app", "email", "fcm"];
+
+export { notificationScenarios, notificationChannels };
diff --git a/server/notifications/utils/scenarioParser.js b/server/notifications/utils/scenarioParser.js
index 11fdaef30..9ad0c47f3 100644
--- a/server/notifications/utils/scenarioParser.js
+++ b/server/notifications/utils/scenarioParser.js
@@ -62,11 +62,27 @@ const scenarioParser = async (req) => {
if (isEmpty(associationsData?.associations)) return;
// Step 6: For each matching scenario, add a scenarioWatchers property
- // that includes only the jobWatchers with the notification setting enabled
+ // that includes only the jobWatchers with at least one notification method enabled.
+ // Each watcher object is formatted as: { user, email, app, fcm }
finalScenarioData.matchingScenarios.forEach((scenario) => {
scenario.scenarioWatchers = associationsData.associations
- .filter((assoc) => assoc.notification_settings && assoc.notification_settings[scenario.key] === true)
- .map((assoc) => assoc.useremail);
+ .filter((assoc) => {
+ // Retrieve the settings object for this scenario (it now contains app, email, and fcm)
+ const settings = assoc.notification_settings && assoc.notification_settings[scenario.key];
+ // Only include this association if at least one notification channel is enabled
+ return settings && (settings.app || settings.email || settings.fcm);
+ })
+ .map((assoc) => {
+ const settings = assoc.notification_settings[scenario.key];
+ return {
+ // Use assoc.user if available, otherwise fallback to assoc.useremail as the identifier
+ user: assoc.user || assoc.useremail,
+ // The email field here is the user's email notification setting (boolean)
+ email: settings.email,
+ app: settings.app,
+ fcm: settings.fcm
+ };
+ });
});
// Step 7: Call builder functions for each matching scenario (fire-and-forget)