1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-11-28 09:24:24 +01:00

(quality) replace Angular-controlled notifications list with react code

This commit is contained in:
Karen 2023-01-30 16:43:54 +01:00 committed by Sylvain
parent 9b69aad9df
commit 7a83a38c68
17 changed files with 498 additions and 241 deletions

View File

@ -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

View File

@ -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<NotificationsIndex> {
const withPage = page ? `?page=${page}` : '';
const res: AxiosResponse<NotificationsIndex> = await apiClient.get(`/api/notifications${withPage}`);
return res?.data;
}
static async update (updatedNotification: Notification): Promise<Notification> {
const res: AxiosResponse<Notification> = await apiClient.patch(`/api/notifications/${updatedNotification.id}`, { notification: updatedNotification });
return res?.data;
}
static async update_all (): Promise<void> {
await apiClient.patch('/api/notifications');
}
}

View File

@ -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<NotificationInlineProps> = ({ 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 (
<div className="notification-inline">
<div className="date">{ FormatLib.date(createdAt) } { FormatLib.time(createdAt) }</div>
<div className="message" dangerouslySetInnerHTML={{ __html: notification.message.description }}/>
{onUpdate && <FabButton onClick={update} className="is-secondary">{ t('app.logged.notification_inline.mark_as_read') }</FabButton>}
</div>
);
};
const NotificationInlineWrapper: React.FC<NotificationInlineProps> = (props) => {
return (
<Loader>
<NotificationInline {...props} />
</Loader>
);
};
export { NotificationInlineWrapper as NotificationInline };

View File

@ -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<NotificationsCenterProps> = ({ onError }) => {
const { t } = useTranslation('admin');
const [isAdmin, setIsAdmin] = useState<boolean>(false);
useEffect(() => {
MemberAPI.current()
.then(data => {
if (data.role === 'admin') setIsAdmin(true);
});
}, []);
return (
<>
{isAdmin && <FabTabs defaultTab='notifications-list' tabs={[
{
id: 'notifications_settings',
title: t('app.admin.notifications_center.notifications_settings'),
content: 'to do notifications_settings'
},
{
id: 'notifications-list',
title: t('app.admin.notifications_center.notifications_list'),
content: <NotificationsList onError={onError}/>
}
]} />}
{!isAdmin && <NotificationsList onError={onError}/>}
</>
);
};
const NotificationsCenterWrapper: React.FC<NotificationsCenterProps> = (props) => {
return (
<Loader>
<NotificationsCenter {...props} />
</Loader>
);
};
Application.Components.component('notificationsCenter', react2angular(NotificationsCenterWrapper, ['onError']));

View File

@ -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<NotificationsListProps> = ({ onError }) => {
const { t } = useTranslation('logged');
const [notifications, setNotifications] = useState<Array<Notification>>([]);
const [totals, setTotals] = useState<NotificationsTotals>({ total: 0, unread: 0 });
const [page, setPage] = useState<number>(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 (
<div className="notifications-list">
<header className="notifications-header">
<h2 className="title">{t('app.logged.notifications_list.notifications')}</h2>
{totals.unread > 0 &&
<FabButton onClick={markAllAsRead} className="is-main">
{ t('app.logged.notifications_list.mark_all_as_read')} ({totals.unread})
</FabButton>}
</header>
{totals.unread === 0 && <p>{ t('app.logged.notifications_list.no_new_notifications') }</p>}
<div>
{newNotifications.map(notification => <NotificationInline key={notification.id} notification={notification} onUpdate={markAsRead} />)}
</div>
{pastNotifications.length > 0 &&
<div className='archives'>
<h3 className="title">{ t('app.logged.notifications_list.archives') }</h3>
{pastNotifications.length === 0
? <p>{ t('app.logged.notifications_list.no_archived_notifications') }</p>
: pastNotifications.map(notification => <NotificationInline key={notification.id} notification={notification} />)
}
</div>
}
{isMoreNotifications &&
<FabButton className="is-black notifications-loader" onClick={loadMoreNotifications}>
{ t('app.logged.notifications_list.load_the_next_notifications') }
</FabButton>
}
</div>
);
};
const NotificationsListWrapper: React.FC<NotificationsListProps> = (props) => {
return (
<Loader>
<NotificationsList {...props} />
</Loader>
);
};
export { NotificationsListWrapper as NotificationsList };

View File

@ -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();
}
]);

View File

@ -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;

View File

@ -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<Notification>
}

View File

@ -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";

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -14,80 +14,6 @@
</div>
</section>
<section class="m-lg">
<div class="row">
<div class="col-md-12">
<button type="button" class="btn btn-warning m-t-sm m-b" ng-click="markAllAsRead()" ng-disabled="totalUnread == 0">{{ 'app.logged.notifications.mark_all_as_read' | translate }} ({{totalUnread}})</button>
<table class="table">
<thead>
<tr>
<th style="width:10%"></th>
<th style="width:20%" translate>{{ 'app.logged.notifications.date' }}</th>
<th style="width:70%" translate>{{ 'app.logged.notifications.notif_title' }}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="notification in notificationsUnread" ng-if="notificationsUnread.length > 0">
<td>
<button class="btn btn-sm btn-warning" ng-click="markAsRead(notification, $event)">
<i class="fa fa-check"></i>
</button>
</td>
<td>{{ notification.created_at | amDateFormat:'LLL' }}</td>
<td ng-bind-html="notification.message.description"></td>
</tr>
<tr ng-if="notificationsUnread.length == 0">
<td colspan="3" translate>{{ 'app.logged.notifications.no_new_notifications' }}</td>
</tr>
</tbody>
</table>
<div ng-hide="notificationsRead.length == 0 && notificationsUnread.length < total">
<h5 translate>{{ 'app.logged.notifications.archives' }}</h5>
<table class="table">
<thead>
<tr>
<th style="width:10%"></th>
<th style="width:20%"></th>
<th style="width:70%"></th>
</tr>
</thead>
<tbody>
<tr class="read" ng-repeat="n in notificationsRead | orderBy:'created_at':true" ng-if="notificationsRead.length > 0">
<td>
</td>
<td>{{ n.created_at | amDateFormat:'LLL' }}</td>
<td ng-bind-html="n.message.description"></td>
</tr>
<tr ng-if="notificationsRead.length == 0">
<td colspan="3" translate>{{ 'app.logged.notifications.no_archived_notifications' }}</td>
</tr>
</tbody>
</table>
</div>
<a class="btn btn-default" ng-click="addMoreNotifications()" ng-if="paginateActive" translate>{{ 'app.logged.notifications.load_the_next_notifications' }}</a>
</div>
</div>
<notifications-center on-error="onError"></notification-center>
</section>

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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."

View File

@ -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