From 7a83a38c68a23cdc6765157fc0ed427fb14d9c74 Mon Sep 17 00:00:00 2001 From: Karen Date: Mon, 30 Jan 2023 16:43:54 +0100 Subject: [PATCH] (quality) replace Angular-controlled notifications list with react code --- .../api/notifications_controller.rb | 7 +- .../src/javascript/api/notification.ts | 20 +++ .../notifications/notification-inline.tsx | 39 +++++ .../notifications/notifications-center.tsx | 57 +++++++ .../notifications/notifications-list.tsx | 109 ++++++++++++ .../javascript/controllers/notifications.js | 95 +---------- .../javascript/models/notification-type.ts | 83 +++++++++ .../src/javascript/models/notification.ts | 27 +++ app/frontend/src/stylesheets/application.scss | 2 + .../notifications/notification-inline.scss | 30 ++++ .../notifications/notifications-list.scss | 26 +++ .../templates/notifications/index.html | 76 +-------- app/models/notification_type.rb | 2 + config/locales/app.admin.en.yml | 3 + config/locales/app.logged.en.yml | 4 + config/locales/mails.en.yml | 2 +- ...0230126160900_create_notification_types.rb | 157 ++++++++++-------- 17 files changed, 498 insertions(+), 241 deletions(-) create mode 100644 app/frontend/src/javascript/api/notification.ts create mode 100644 app/frontend/src/javascript/components/notifications/notification-inline.tsx create mode 100644 app/frontend/src/javascript/components/notifications/notifications-center.tsx create mode 100644 app/frontend/src/javascript/components/notifications/notifications-list.tsx create mode 100644 app/frontend/src/javascript/models/notification-type.ts create mode 100644 app/frontend/src/javascript/models/notification.ts create mode 100644 app/frontend/src/stylesheets/modules/notifications/notification-inline.scss create mode 100644 app/frontend/src/stylesheets/modules/notifications/notifications-list.scss diff --git a/app/controllers/api/notifications_controller.rb b/app/controllers/api/notifications_controller.rb index c99d78579..606cb4914 100644 --- a/app/controllers/api/notifications_controller.rb +++ b/app/controllers/api/notifications_controller.rb @@ -25,7 +25,7 @@ class API::NotificationsController < API::ApiController end @totals = { total: current_user.notifications.delivered_in_system(current_user).count, - unread: current_user.notifications.where(is_read: false).count + unread: current_user.notifications.delivered_in_system(current_user).where(is_read: false).count } render :index end @@ -33,6 +33,7 @@ class API::NotificationsController < API::ApiController def last_unread loop do @notifications = current_user.notifications + .delivered_in_system(current_user) .includes(:attached_object) .where(is_read: false) .limit(3) @@ -42,7 +43,7 @@ class API::NotificationsController < API::ApiController end @totals = { total: current_user.notifications.delivered_in_system(current_user).count, - unread: current_user.notifications.where(is_read: false).count + unread: current_user.notifications.delivered_in_system(current_user).where(is_read: false).count } render :index end @@ -53,7 +54,7 @@ class API::NotificationsController < API::ApiController .order('created_at DESC') @totals = { total: current_user.notifications.delivered_in_system(current_user).count, - unread: current_user.notifications.where(is_read: false).count + unread: current_user.notifications.delivered_in_system(current_user).where(is_read: false).count } render :index end diff --git a/app/frontend/src/javascript/api/notification.ts b/app/frontend/src/javascript/api/notification.ts new file mode 100644 index 000000000..7a8cd4ddb --- /dev/null +++ b/app/frontend/src/javascript/api/notification.ts @@ -0,0 +1,20 @@ +import apiClient from './clients/api-client'; +import { AxiosResponse } from 'axios'; +import { NotificationsIndex, Notification } from '../models/notification'; + +export default class NotificationAPI { + static async index (page?: number): Promise { + const withPage = page ? `?page=${page}` : ''; + const res: AxiosResponse = await apiClient.get(`/api/notifications${withPage}`); + return res?.data; + } + + static async update (updatedNotification: Notification): Promise { + const res: AxiosResponse = await apiClient.patch(`/api/notifications/${updatedNotification.id}`, { notification: updatedNotification }); + return res?.data; + } + + static async update_all (): Promise { + await apiClient.patch('/api/notifications'); + } +} diff --git a/app/frontend/src/javascript/components/notifications/notification-inline.tsx b/app/frontend/src/javascript/components/notifications/notification-inline.tsx new file mode 100644 index 000000000..89665d0db --- /dev/null +++ b/app/frontend/src/javascript/components/notifications/notification-inline.tsx @@ -0,0 +1,39 @@ +import { Loader } from '../base/loader'; +import { Notification } from '../../models/notification'; +import FormatLib from '../../lib/format'; +import { FabButton } from '../base/fab-button'; +import { useTranslation } from 'react-i18next'; + +interface NotificationInlineProps { + notification: Notification, + onUpdate?: (Notification) => void +} + +/** + * Displays one notification + */ +const NotificationInline: React.FC = ({ notification, onUpdate }) => { + const { t } = useTranslation('logged'); + const createdAt = new Date(notification.created_at); + + // Call a parent component method to update the notification + const update = () => onUpdate(notification); + + return ( +
+
{ FormatLib.date(createdAt) } { FormatLib.time(createdAt) }
+
+ {onUpdate && { t('app.logged.notification_inline.mark_as_read') }} +
+ ); +}; + +const NotificationInlineWrapper: React.FC = (props) => { + return ( + + + + ); +}; + +export { NotificationInlineWrapper as NotificationInline }; diff --git a/app/frontend/src/javascript/components/notifications/notifications-center.tsx b/app/frontend/src/javascript/components/notifications/notifications-center.tsx new file mode 100644 index 000000000..60424904e --- /dev/null +++ b/app/frontend/src/javascript/components/notifications/notifications-center.tsx @@ -0,0 +1,57 @@ +import { useState, useEffect } from 'react'; +import { IApplication } from '../../models/application'; +import { react2angular } from 'react2angular'; +import { Loader } from '../base/loader'; +import { FabTabs } from '../base/fab-tabs'; +import { NotificationsList } from './notifications-list'; +import { useTranslation } from 'react-i18next'; +import MemberAPI from '../../api/member'; + +declare const Application: IApplication; + +interface NotificationsCenterProps { + onError: (message: string) => void +} + +/** + * This Admin component groups two tabs : a list of notifications and the notifications settings + */ +export const NotificationsCenter: React.FC = ({ onError }) => { + const { t } = useTranslation('admin'); + const [isAdmin, setIsAdmin] = useState(false); + + useEffect(() => { + MemberAPI.current() + .then(data => { + if (data.role === 'admin') setIsAdmin(true); + }); + }, []); + + return ( + <> + {isAdmin && + } + ]} />} + {!isAdmin && } + + ); +}; + +const NotificationsCenterWrapper: React.FC = (props) => { + return ( + + + + ); +}; + +Application.Components.component('notificationsCenter', react2angular(NotificationsCenterWrapper, ['onError'])); diff --git a/app/frontend/src/javascript/components/notifications/notifications-list.tsx b/app/frontend/src/javascript/components/notifications/notifications-list.tsx new file mode 100644 index 000000000..839ac2eb6 --- /dev/null +++ b/app/frontend/src/javascript/components/notifications/notifications-list.tsx @@ -0,0 +1,109 @@ +import { useEffect, useState } from 'react'; +import { Loader } from '../base/loader'; +import { Notification, NotificationsTotals } from '../../models/notification'; +import NotificationAPI from '../../api/notification'; +import { useTranslation } from 'react-i18next'; +import { NotificationInline } from './notification-inline'; +import { FabButton } from '../base/fab-button'; + +interface NotificationsListProps { + onError: (message: string) => void +} + +/** + * Displays the list of notifications + */ +const NotificationsList: React.FC = ({ onError }) => { + const { t } = useTranslation('logged'); + + const [notifications, setNotifications] = useState>([]); + const [totals, setTotals] = useState({ total: 0, unread: 0 }); + const [page, setPage] = useState(1); + + const newNotifications = notifications.filter(notification => notification.is_read === false); + const pastNotifications = notifications.filter(notification => notification.is_read === true); + + // Fetch Notification and Notification Totals from API + const fetchNotifications = () => { + NotificationAPI.index() + .then(data => { + setTotals(data.totals); + setNotifications(data.notifications); + }) + .catch(onError); + }; + + // Fetch Notifications and Notification Totals on component mount + useEffect(() => { + fetchNotifications(); + }, []); + + // Call Notifications API to set one notification as read, and fetch the updated Notifications & Totals + const markAsRead = async (notification: Notification) => { + await NotificationAPI.update(notification); + fetchNotifications(); + }; + + // Call Notifications API to set all notifications as read, and fetch the updated Notifications & Totals + const markAllAsRead = async () => { + await NotificationAPI.update_all(); + fetchNotifications(); + }; + + // Calculate if they are notifications that are not yet displayed + // If true, allows user to display more notifications + const isMoreNotifications = (totals.total - notifications.length) > 0; + + // Call API to Load More Notifications + const loadMoreNotifications = () => { + if (isMoreNotifications) { + const nextPage = page + 1; + NotificationAPI.index(nextPage) + .then(data => { + setNotifications(prevState => [...prevState, ...data.notifications]); + setPage(nextPage); + }) + .catch(onError); + } + }; + + return ( +
+
+

{t('app.logged.notifications_list.notifications')}

+ {totals.unread > 0 && + + { t('app.logged.notifications_list.mark_all_as_read')} ({totals.unread}) + } +
+ {totals.unread === 0 &&

{ t('app.logged.notifications_list.no_new_notifications') }

} +
+ {newNotifications.map(notification => )} +
+ {pastNotifications.length > 0 && +
+

{ t('app.logged.notifications_list.archives') }

+ {pastNotifications.length === 0 + ?

{ t('app.logged.notifications_list.no_archived_notifications') }

+ : pastNotifications.map(notification => ) + } +
+ } + {isMoreNotifications && + + { t('app.logged.notifications_list.load_the_next_notifications') } + + } +
+ ); +}; + +const NotificationsListWrapper: React.FC = (props) => { + return ( + + + + ); +}; + +export { NotificationsListWrapper as NotificationsList }; diff --git a/app/frontend/src/javascript/controllers/notifications.js b/app/frontend/src/javascript/controllers/notifications.js index 0e979b031..48ec0debc 100644 --- a/app/frontend/src/javascript/controllers/notifications.js +++ b/app/frontend/src/javascript/controllers/notifications.js @@ -14,100 +14,15 @@ /** * Controller used in notifications page - * inherits $scope.$parent.notifications (global notifications state) from ApplicationController */ -Application.Controllers.controller('NotificationsController', ['$scope', 'Notification', function ($scope, Notification) { +Application.Controllers.controller('NotificationsController', ['$scope', 'growl', function ($scope, growl) { /* PUBLIC SCOPE */ - // Array containg the archived notifications (already read) - $scope.notificationsRead = []; - - // Array containg the new notifications (not read) - $scope.notificationsUnread = []; - - // Total number of notifications for the current user - $scope.total = 0; - - // Total number of unread notifications for the current user - $scope.totalUnread = 0; - - // By default, the pagination mode is activated to limit the page size - $scope.paginateActive = true; - - // The currently displayed page number - $scope.page = 1; - /** - * Mark the provided notification as read, updating its status on the server and moving it - * to the already read notifications list. - * @param notification {{id:number}} the notification to mark as read - * @param e {Object} see https://docs.angularjs.org/guide/expression#-event- - */ - $scope.markAsRead = function (notification, e) { - e.preventDefault(); - return Notification.update({ id: notification.id }, { - id: notification.id, - is_read: true - } - , function (updatedNotif) { - // remove notif from unreads - const index = $scope.notificationsUnread.indexOf(notification); - $scope.notificationsUnread.splice(index, 1); - // add update notif to read - $scope.notificationsRead.push(updatedNotif); - // update counters - $scope.$parent.notifications.unread -= 1; - return $scope.totalUnread -= 1; - }); + * Shows an error message forwarded from a child react component + */ + $scope.onError = function (message) { + growl.error(message); }; - - /** - * Mark every unread notifications as read and move them for the unread list to to read array. - */ - $scope.markAllAsRead = () => - Notification.update({} - , function () { // success - // add notifs to read - angular.forEach($scope.notificationsUnread, function (n) { - n.is_read = true; - return $scope.notificationsRead.push(n); - }); - // clear unread - $scope.notificationsUnread = []; - // update counters - $scope.$parent.notifications.unread = 0; - return $scope.totalUnread = 0; - }); - - /** - * Request the server to retrieve the next notifications and add them - * to their corresponding notifications list (read or unread). - */ - $scope.addMoreNotifications = function () { - Notification.query({ page: $scope.page }, function (notifications) { - $scope.total = notifications.totals.total; - $scope.totalUnread = notifications.totals.unread; - angular.forEach(notifications.notifications, function (notif) { - if (notif.is_read) { - return $scope.notificationsRead.push(notif); - } else { - return $scope.notificationsUnread.push(notif); - } - }); - return $scope.paginateActive = (notifications.totals.total > ($scope.notificationsRead.length + $scope.notificationsUnread.length)); - }); - - return $scope.page += 1; - }; - - /* PRIVATE SCOPE */ - - /** - * Kind of constructor: these actions will be realized first when the controller is loaded - */ - const initialize = () => $scope.addMoreNotifications(); - - // !!! MUST BE CALLED AT THE END of the controller - return initialize(); } ]); diff --git a/app/frontend/src/javascript/models/notification-type.ts b/app/frontend/src/javascript/models/notification-type.ts new file mode 100644 index 000000000..b1a310026 --- /dev/null +++ b/app/frontend/src/javascript/models/notification-type.ts @@ -0,0 +1,83 @@ +import { ApiFilter } from '../models/api'; + +export interface NotificationType { + id: number, + name: typeof notificationTypeNames[number], + category: string, + is_configurable: boolean +} + +export interface NotificationTypeIndexFilter extends ApiFilter { + is_configurable?: boolean +} + +export const notificationTypeNames = [ + 'notify_admin_when_project_published', + 'notify_project_collaborator_to_valid', + 'notify_project_author_when_collaborator_valid', + 'notify_user_training_valid', + 'notify_member_subscribed_plan', + 'notify_member_create_reservation', + 'notify_member_subscribed_plan_is_changed', + 'notify_admin_member_create_reservation', + 'notify_member_slot_is_modified', + 'notify_admin_slot_is_modified', + 'notify_admin_when_user_is_created', + 'notify_admin_subscribed_plan', + 'notify_user_when_invoice_ready', + 'notify_member_subscription_will_expire_in_7_days', + 'notify_member_subscription_is_expired', + 'notify_admin_subscription_will_expire_in_7_days', + 'notify_admin_subscription_is_expired', + 'notify_admin_subscription_canceled', + 'notify_member_subscription_canceled', + 'notify_user_when_avoir_ready', + 'notify_member_slot_is_canceled', + 'notify_admin_slot_is_canceled', + 'notify_partner_subscribed_plan', + 'notify_member_subscription_extended', + 'notify_admin_subscription_extended', + 'notify_admin_user_group_changed', + 'notify_user_user_group_changed', + 'notify_admin_when_user_is_imported', + 'notify_user_profile_complete', + 'notify_user_auth_migration', + 'notify_admin_user_merged', + 'notify_admin_profile_complete', + 'notify_admin_abuse_reported', + 'notify_admin_invoicing_changed', + 'notify_user_wallet_is_credited', + 'notify_admin_user_wallet_is_credited', + 'notify_admin_export_complete', + 'notify_member_about_coupon', + 'notify_member_reservation_reminder', + 'notify_admin_free_disk_space', + 'notify_admin_close_period_reminder', + 'notify_admin_archive_complete', + 'notify_privacy_policy_changed', + 'notify_admin_import_complete', + 'notify_admin_refund_created', + 'notify_admins_role_update', + 'notify_user_role_update', + 'notify_admin_objects_stripe_sync', + 'notify_user_when_payment_schedule_ready', + 'notify_admin_payment_schedule_failed', + 'notify_member_payment_schedule_failed', + 'notify_admin_payment_schedule_check_deadline', + 'notify_admin_payment_schedule_transfer_deadline', + 'notify_admin_payment_schedule_error', + 'notify_member_payment_schedule_error', + 'notify_admin_payment_schedule_gateway_canceled', + 'notify_member_payment_schedule_gateway_canceled', + 'notify_admin_user_proof_of_identity_files_created', + 'notify_admin_user_proof_of_identity_files_updated', + 'notify_user_is_validated', + 'notify_user_is_invalidated', + 'notify_user_proof_of_identity_refusal', + 'notify_admin_user_proof_of_identity_refusal', + 'notify_user_order_is_ready', + 'notify_user_order_is_canceled', + 'notify_user_order_is_refunded', + 'notify_admin_low_stock_threshold', + 'notify_admin_training_auto_cancelled' +] as const; diff --git a/app/frontend/src/javascript/models/notification.ts b/app/frontend/src/javascript/models/notification.ts new file mode 100644 index 000000000..564b33ea9 --- /dev/null +++ b/app/frontend/src/javascript/models/notification.ts @@ -0,0 +1,27 @@ +import { TDateISO } from '../typings/date-iso'; + +import { notificationTypeNames } from './notification-type'; + +export type NotificationName = typeof notificationTypeNames[number]; + +export interface Notification { + id: number, + notification_type_id: number, + notification_type: NotificationName, + created_at: TDateISO, + is_read: boolean, + message: { + title: string, + description: string + } +} + +export interface NotificationsTotals { + total: number, + unread: number +} + +export interface NotificationsIndex { + totals: NotificationsTotals, + notifications: Array +} diff --git a/app/frontend/src/stylesheets/application.scss b/app/frontend/src/stylesheets/application.scss index 990e450ef..8e3bf4dde 100644 --- a/app/frontend/src/stylesheets/application.scss +++ b/app/frontend/src/stylesheets/application.scss @@ -74,6 +74,8 @@ @import "modules/machines/machines-list"; @import "modules/machines/machines-settings"; @import "modules/machines/required-training-modal"; +@import "modules/notifications/notifications-list"; +@import "modules/notifications/notification-line"; @import "modules/payment-schedule/payment-schedule-dashboard"; @import "modules/payment-schedule/payment-schedule-summary"; @import "modules/payment-schedule/payment-schedules-list"; diff --git a/app/frontend/src/stylesheets/modules/notifications/notification-inline.scss b/app/frontend/src/stylesheets/modules/notifications/notification-inline.scss new file mode 100644 index 000000000..5117137a4 --- /dev/null +++ b/app/frontend/src/stylesheets/modules/notifications/notification-inline.scss @@ -0,0 +1,30 @@ +.notification-inline { + padding: 2rem 0; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--gray-soft-darkest); + + &:last-child { + border-bottom: none; + } + + .date { + width: 160px; + flex-shrink: 0; + } + + .message { + margin: 0 4rem; + flex-grow: 1; + text-align: left; + } + + @media (max-width: 1054px) { + flex-direction: column; + gap: 2rem; + .message { + margin: 0; + } + } +} diff --git a/app/frontend/src/stylesheets/modules/notifications/notifications-list.scss b/app/frontend/src/stylesheets/modules/notifications/notifications-list.scss new file mode 100644 index 000000000..fbb06d98d --- /dev/null +++ b/app/frontend/src/stylesheets/modules/notifications/notifications-list.scss @@ -0,0 +1,26 @@ +.notifications-list { + max-width: 1200px; + margin: 0 auto; + padding-bottom: 6rem; + display: flex; + flex-direction: column; + + .notifications-header { @include header; } + + .archives { + margin: 2rem 0; + padding: 1rem 3rem; + border-radius: 5px; + background-color: var(--gray-soft-light); + + .title { + @include title-base; + color: var(--gray-hard-darkest) !important; + } + + } + .notifications-loader { + width: fit-content; + margin-top: 2rem; + } +} diff --git a/app/frontend/templates/notifications/index.html b/app/frontend/templates/notifications/index.html index db4af597d..681a76518 100644 --- a/app/frontend/templates/notifications/index.html +++ b/app/frontend/templates/notifications/index.html @@ -14,80 +14,6 @@
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - -
{{ 'app.logged.notifications.date' }}{{ 'app.logged.notifications.notif_title' }}
- - {{ notification.created_at | amDateFormat:'LLL' }}
{{ 'app.logged.notifications.no_new_notifications' }}
- -
-
{{ 'app.logged.notifications.archives' }}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- {{ n.created_at | amDateFormat:'LLL' }}
{{ 'app.logged.notifications.no_archived_notifications' }}
-
- - {{ 'app.logged.notifications.load_the_next_notifications' }} - -
- -
+
- - - - diff --git a/app/models/notification_type.rb b/app/models/notification_type.rb index b8fcc007b..a40129802 100644 --- a/app/models/notification_type.rb +++ b/app/models/notification_type.rb @@ -6,4 +6,6 @@ class NotificationType < ApplicationRecord has_many :notification_preferences, dependent: :destroy validates :name, uniqueness: true, presence: true + validates :category, presence: true + validates :is_configurable, inclusion: { in: [true, false] } end diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index ef17bfd54..86ad61656 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -2412,3 +2412,6 @@ en: cta_switch: "Display a button" cta_label: "Button label" cta_url: "Button link" + notifications_center: + notifications_list: "All notifications" + notifications_settings: "My notifications preferences" diff --git a/config/locales/app.logged.en.yml b/config/locales/app.logged.en.yml index 10f3f61c6..81ba609f7 100644 --- a/config/locales/app.logged.en.yml +++ b/config/locales/app.logged.en.yml @@ -251,6 +251,8 @@ en: i_change: "I change" notifications: notifications_center: "Notifications center" + notifications_list: + notifications: "All notifications" mark_all_as_read: "Mark all as read" date: "Date" notif_title: "Title" @@ -258,3 +260,5 @@ en: archives: "Archives" no_archived_notifications: "No archived notifications." load_the_next_notifications: "Load the next notifications..." + notification_line: + mark_as_read: "Mark as read" diff --git a/config/locales/mails.en.yml b/config/locales/mails.en.yml index 13ccc0ba1..e1cc4b683 100644 --- a/config/locales/mails.en.yml +++ b/config/locales/mails.en.yml @@ -21,7 +21,7 @@ en: token_if_link_problem: "If you experience issues with the link, you can enter the following code at your first connection attempt:" notifications_mailer: notify_user_user_group_changed: - subject: "Your have changed group" + subject: "You have changed group" body: warning: "You have changed group. Inspections can be conducted at the lab to verify the legitimacy of this change." user_invalidated: "Your account was invalidated, please upload your new supporting documents to validate your account." diff --git a/db/migrate/20230126160900_create_notification_types.rb b/db/migrate/20230126160900_create_notification_types.rb index 565b773ee..e9d819024 100644 --- a/db/migrate/20230126160900_create_notification_types.rb +++ b/db/migrate/20230126160900_create_notification_types.rb @@ -6,80 +6,89 @@ # This migration is linked to the abandon of the NotifyWith gem. Notification Types # will now be store in database and we will manage ourself the Notification system. class CreateNotificationTypes < ActiveRecord::Migration[5.2] - NAMES = %w[ - notify_admin_when_project_published - notify_project_collaborator_to_valid - notify_project_author_when_collaborator_valid - notify_user_training_valid - notify_member_subscribed_plan - notify_member_create_reservation - notify_member_subscribed_plan_is_changed - notify_admin_member_create_reservation - notify_member_slot_is_modified - notify_admin_slot_is_modified - notify_admin_when_user_is_created - notify_admin_subscribed_plan - notify_user_when_invoice_ready - notify_member_subscription_will_expire_in_7_days - notify_member_subscription_is_expired - notify_admin_subscription_will_expire_in_7_days - notify_admin_subscription_is_expired - notify_admin_subscription_canceled - notify_member_subscription_canceled - notify_user_when_avoir_ready - notify_member_slot_is_canceled - notify_admin_slot_is_canceled - notify_partner_subscribed_plan - notify_member_subscription_extended - notify_admin_subscription_extended - notify_admin_user_group_changed - notify_user_user_group_changed - notify_admin_when_user_is_imported - notify_user_profile_complete - notify_user_auth_migration - notify_admin_user_merged - notify_admin_profile_complete - notify_admin_abuse_reported - notify_admin_invoicing_changed - notify_user_wallet_is_credited - notify_admin_user_wallet_is_credited - notify_admin_export_complete - notify_member_about_coupon - notify_member_reservation_reminder - notify_admin_free_disk_space - notify_admin_close_period_reminder - notify_admin_archive_complete - notify_privacy_policy_changed - notify_admin_import_complete - notify_admin_refund_created - notify_admins_role_update - notify_user_role_update - notify_admin_objects_stripe_sync - notify_user_when_payment_schedule_ready - notify_admin_payment_schedule_failed - notify_member_payment_schedule_failed - notify_admin_payment_schedule_check_deadline - notify_admin_payment_schedule_transfer_deadline - notify_admin_payment_schedule_error - notify_member_payment_schedule_error - notify_admin_payment_schedule_gateway_canceled - notify_member_payment_schedule_gateway_canceled - notify_admin_user_proof_of_identity_files_created - notify_admin_user_proof_of_identity_files_updated - notify_user_is_validated - notify_user_is_invalidated - notify_user_proof_of_identity_refusal - notify_admin_user_proof_of_identity_refusal - notify_user_order_is_ready - notify_user_order_is_canceled - notify_user_order_is_refunded - notify_admin_low_stock_threshold - notify_admin_training_auto_cancelled + # Index start at 1. This is required due to previous functionning of the NotifyWith gem + NOTIFICATIONS_TYPES = [ + { id: 1, name: 'notify_admin_when_project_published', category: 'projects', is_configurable: true }, + { id: 2, name: 'notify_project_collaborator_to_valid', category: 'projects', is_configurable: true }, + { id: 3, name: 'notify_project_author_when_collaborator_valid', category: 'projects', is_configurable: true }, + { id: 4, name: 'notify_user_training_valid', category: 'trainings', is_configurable: false }, + { id: 5, name: 'notify_member_subscribed_plan', category: 'subscriptions', is_configurable: false }, + { id: 6, name: 'notify_member_create_reservation', category: 'agenda', is_configurable: false }, + { id: 7, name: 'notify_member_subscribed_plan_is_changed', category: 'deprecated', is_configurable: false }, + { id: 8, name: 'notify_admin_member_create_reservation', category: 'agenda', is_configurable: true }, + { id: 9, name: 'notify_member_slot_is_modified', category: 'agenda', is_configurable: false }, + { id: 10, name: 'notify_admin_slot_is_modified', category: 'agenda', is_configurable: true }, + + { id: 11, name: 'notify_admin_when_user_is_created', category: 'users_accounts', is_configurable: true }, + { id: 12, name: 'notify_admin_subscribed_plan', category: 'subscriptions', is_configurable: true }, + { id: 13, name: 'notify_user_when_invoice_ready', category: 'payments', is_configurable: true }, + { id: 14, name: 'notify_member_subscription_will_expire_in_7_days', category: 'subscriptions', is_configurable: false }, + { id: 15, name: 'notify_member_subscription_is_expired', category: 'subscriptions', is_configurable: false }, + { id: 16, name: 'notify_admin_subscription_will_expire_in_7_days', category: 'subscriptions', is_configurable: true }, + { id: 17, name: 'notify_admin_subscription_is_expired', category: 'subscriptions', is_configurable: true }, + { id: 18, name: 'notify_admin_subscription_canceled', category: 'subscriptions', is_configurable: true }, + { id: 19, name: 'notify_member_subscription_canceled', category: 'subscriptions', is_configurable: false }, + { id: 20, name: 'notify_user_when_avoir_ready', category: 'wallet', is_configurable: false }, + + { id: 21, name: 'notify_member_slot_is_canceled', category: 'agenda', is_configurable: false }, + { id: 22, name: 'notify_admin_slot_is_canceled', category: 'agenda', is_configurable: true }, + { id: 23, name: 'notify_partner_subscribed_plan', category: 'subscriptions', is_configurable: false }, + { id: 24, name: 'notify_member_subscription_extended', category: 'subscriptions', is_configurable: false }, + { id: 25, name: 'notify_admin_subscription_extended', category: 'subscriptions', is_configurable: true }, + { id: 26, name: 'notify_admin_user_group_changed', category: 'users_accounts', is_configurable: true }, + { id: 27, name: 'notify_user_user_group_changed', category: 'users_accounts', is_configurable: false }, + { id: 28, name: 'notify_admin_when_user_is_imported', category: 'users_accounts', is_configurable: true }, + { id: 29, name: 'notify_user_profile_complete', category: 'users_accounts', is_configurable: false }, + { id: 30, name: 'notify_user_auth_migration', category: 'user', is_configurable: false }, + + { id: 31, name: 'notify_admin_user_merged', category: 'users_accounts', is_configurable: true }, + { id: 32, name: 'notify_admin_profile_complete', category: 'users_accounts', is_configurable: true }, + { id: 33, name: 'notify_admin_abuse_reported', category: 'projects', is_configurable: true }, + { id: 34, name: 'notify_admin_invoicing_changed', category: 'deprecated', is_configurable: false }, + { id: 35, name: 'notify_user_wallet_is_credited', category: 'wallet', is_configurable: false }, + { id: 36, name: 'notify_admin_user_wallet_is_credited', category: 'wallet', is_configurable: true }, + { id: 37, name: 'notify_admin_export_complete', category: 'exports', is_configurable: false }, + { id: 38, name: 'notify_member_about_coupon', category: 'agenda', is_configurable: false }, + { id: 39, name: 'notify_member_reservation_reminder', category: 'agenda', is_configurable: false }, + + { id: 40, name: 'notify_admin_free_disk_space', category: 'app_management', is_configurable: false }, + { id: 41, name: 'notify_admin_close_period_reminder', category: 'accountings', is_configurable: true }, + { id: 42, name: 'notify_admin_archive_complete', category: 'accountings', is_configurable: true }, + { id: 43, name: 'notify_privacy_policy_changed', category: 'app_management', is_configurable: false }, + { id: 44, name: 'notify_admin_import_complete', category: 'app_management', is_configurable: false }, + { id: 45, name: 'notify_admin_refund_created', category: 'wallet', is_configurable: true }, + { id: 46, name: 'notify_admins_role_update', category: 'users_accounts', is_configurable: true }, + { id: 47, name: 'notify_user_role_update', category: 'users_accounts', is_configurable: false }, + { id: 48, name: 'notify_admin_objects_stripe_sync', category: 'payments', is_configurable: false }, + { id: 49, name: 'notify_user_when_payment_schedule_ready', category: 'payments', is_configurable: false }, + + { id: 50, name: 'notify_admin_payment_schedule_failed', category: 'payments', is_configurable: true }, + { id: 51, name: 'notify_member_payment_schedule_failed', category: 'payments', is_configurable: false }, + { id: 52, name: 'notify_admin_payment_schedule_check_deadline', category: 'payments', is_configurable: true }, + { id: 53, name: 'notify_admin_payment_schedule_transfer_deadline', category: 'payments', is_configurable: true }, + { id: 54, name: 'notify_admin_payment_schedule_error', category: 'payments', is_configurable: true }, + { id: 55, name: 'notify_member_payment_schedule_error', category: 'payments', is_configurable: false }, + { id: 56, name: 'notify_admin_payment_schedule_gateway_canceled', category: 'payments', is_configurable: true }, + { id: 57, name: 'notify_member_payment_schedule_gateway_canceled', category: 'payments', is_configurable: false }, + { id: 58, name: 'notify_admin_user_proof_of_identity_files_created', category: 'proof_of_identity', is_configurable: true }, + { id: 59, name: 'notify_admin_user_proof_of_identity_files_updated', category: 'proof_of_identity', is_configurable: true }, + + { id: 60, name: 'notify_user_is_validated', category: 'users_accounts', is_configurable: false }, + { id: 61, name: 'notify_user_is_invalidated', category: 'users_accounts', is_configurable: false }, + { id: 62, name: 'notify_user_proof_of_identity_refusal', category: 'proof_of_identity', is_configurable: false }, + { id: 63, name: 'notify_admin_user_proof_of_identity_refusal', category: 'proof_of_identity', is_configurable: true }, + { id: 64, name: 'notify_user_order_is_ready', category: 'shop', is_configurable: true }, + { id: 65, name: 'notify_user_order_is_canceled', category: 'shop', is_configurable: true }, + { id: 66, name: 'notify_user_order_is_refunded', category: 'shop', is_configurable: true }, + { id: 67, name: 'notify_admin_low_stock_threshold', category: 'shop', is_configurable: true }, + { id: 68, name: 'notify_admin_training_auto_cancelled', category: 'trainings', is_configurable: true } ].freeze def up create_table :notification_types do |t| t.string :name, null: false + t.string :category, null: false + t.boolean :is_configurable, null: false t.timestamps end @@ -87,9 +96,13 @@ class CreateNotificationTypes < ActiveRecord::Migration[5.2] add_index :notification_types, :name, unique: true # Records previous notification types - # Index start at 1. This is required due to previous functionning of the NotifyWith gem - NAMES.each.with_index(1) do |type, index| - NotificationType.create!(id: index, name: type) + NOTIFICATIONS_TYPES.each do |notification_type| + NotificationType.create!( + id: notification_type[:id], + name: notification_type[:name], + category: notification_type[:category], + is_configurable: notification_type[:is_configurable] + ) end last_id = NotificationType.order(:id).last.id