mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-17 11:54:22 +01:00
(feat) Admin can control its notifications preferences
This commit is contained in:
parent
7a83a38c68
commit
10473182d4
50
app/controllers/api/notification_preferences_controller.rb
Normal file
50
app/controllers/api/notification_preferences_controller.rb
Normal file
@ -0,0 +1,50 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# API Controller for resources of type Notification Preferences
|
||||
class API::NotificationPreferencesController < API::ApiController
|
||||
before_action :authenticate_user!
|
||||
|
||||
def index
|
||||
@notification_preferences = current_user.notification_preferences
|
||||
end
|
||||
|
||||
# Currently only available for Admin in NotificationPreferencePolicy
|
||||
def update
|
||||
authorize NotificationPreference
|
||||
notification_type = NotificationType.find_by(name: params[:notification_preference][:notification_type])
|
||||
@notification_preference = NotificationPreference.find_or_create_by(notification_type: notification_type, user: current_user)
|
||||
@notification_preference.update(notification_preference_params)
|
||||
|
||||
if @notification_preference.save
|
||||
render :show, status: :ok
|
||||
else
|
||||
render json: @notification_preference.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
# Currently only available for Admin in NotificationPreferencePolicy
|
||||
def bulk_update
|
||||
authorize NotificationPreference
|
||||
errors = []
|
||||
params[:notification_preferences].each do |notification_preference|
|
||||
notification_type = NotificationType.find_by(name: notification_preference[:notification_type])
|
||||
db_notification_preference = NotificationPreference.find_or_create_by(notification_type_id: notification_type.id, user: current_user)
|
||||
|
||||
next if db_notification_preference.update(email: notification_preference[:email], in_system: notification_preference[:in_system])
|
||||
|
||||
errors.push(db_notification_preference.errors)
|
||||
end
|
||||
|
||||
if errors.any?
|
||||
render json: errors, status: :unprocessable_entity
|
||||
else
|
||||
head :no_content, status: :ok
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def notification_preference_params
|
||||
params.require(:notification_preference).permit(:notification_type_id, :in_system, :email)
|
||||
end
|
||||
end
|
14
app/controllers/api/notification_types_controller.rb
Normal file
14
app/controllers/api/notification_types_controller.rb
Normal file
@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# API Controller for resources of type Notification Types
|
||||
class API::NotificationTypesController < API::ApiController
|
||||
before_action :authenticate_user!
|
||||
|
||||
def index
|
||||
@notification_types = if params[:is_configurable] == 'true'
|
||||
NotificationType.where(is_configurable: true)
|
||||
else
|
||||
NotificationType.all
|
||||
end
|
||||
end
|
||||
end
|
20
app/frontend/src/javascript/api/notification_preference.ts
Normal file
20
app/frontend/src/javascript/api/notification_preference.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import apiClient from './clients/api-client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { NotificationPreference } from '../models/notification-preference';
|
||||
|
||||
export default class NotificationPreferencesAPI {
|
||||
static async index (): Promise<Array<NotificationPreference>> {
|
||||
const res: AxiosResponse<Array<NotificationPreference>> = await apiClient.get('/api/notification_preferences');
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async update (updatedPreference: NotificationPreference): Promise<NotificationPreference> {
|
||||
const res: AxiosResponse<NotificationPreference> = await apiClient.patch(`/api/notification_preferences/${updatedPreference.notification_type}`, { notification_preference: updatedPreference });
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async bulk_update (updatedPreferences: Array<NotificationPreference>): Promise<NotificationPreference> {
|
||||
const res: AxiosResponse<NotificationPreference> = await apiClient.patch('/api/notification_preferences/bulk_update', { notification_preferences: updatedPreferences });
|
||||
return res?.data;
|
||||
}
|
||||
}
|
11
app/frontend/src/javascript/api/notification_types.ts
Normal file
11
app/frontend/src/javascript/api/notification_types.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import apiClient from './clients/api-client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { NotificationTypeIndexFilter, NotificationType } from '../models/notification-type';
|
||||
import ApiLib from '../lib/api';
|
||||
|
||||
export default class NotificationTypesAPI {
|
||||
static async index (isConfigurable?:NotificationTypeIndexFilter): Promise<Array<NotificationType>> {
|
||||
const res: AxiosResponse<Array<NotificationType>> = await apiClient.get(`/api/notification_types${ApiLib.filtersToQuery(isConfigurable)}`);
|
||||
return res?.data;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Loader } from '../base/loader';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NotificationPreference } from '../../models/notification-preference';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { FormSwitch } from '../form/form-switch';
|
||||
import NotificationPreferencesAPI from '../../api/notification_preference';
|
||||
|
||||
interface NotificationFormProps {
|
||||
onError: (message: string) => void,
|
||||
preference: NotificationPreference
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the list of notifications
|
||||
*/
|
||||
const NotificationForm: React.FC<NotificationFormProps> = ({ preference, onError }) => {
|
||||
const { t } = useTranslation('logged');
|
||||
const { handleSubmit, formState, control, reset } = useForm<NotificationPreference>({ defaultValues: { ...preference } });
|
||||
|
||||
// Create or Update (if id exists) a Notification Preference
|
||||
const onSubmit = (updatedPreference: NotificationPreference) => NotificationPreferencesAPI.update(updatedPreference).catch(onError);
|
||||
|
||||
// Calls submit handler on every change of a Form Switch
|
||||
const handleChange = () => handleSubmit(onSubmit)();
|
||||
|
||||
// Resets form on component mount, and if preference changes (happens when bulk updating a category)
|
||||
useEffect(() => {
|
||||
reset(preference);
|
||||
}, [preference]);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="notification-form">
|
||||
<p className="notification-type">{t(`app.logged.notification_form.${preference.notification_type}`)}</p>
|
||||
<div className='form-actions'>
|
||||
<FormSwitch
|
||||
className="form-action"
|
||||
control={control}
|
||||
formState={formState}
|
||||
defaultValue={preference.email}
|
||||
id="email"
|
||||
label='email'
|
||||
onChange={handleChange}/>
|
||||
<FormSwitch
|
||||
className="form-action"
|
||||
control={control}
|
||||
formState={formState}
|
||||
defaultValue={preference.in_system}
|
||||
id="in_system"
|
||||
label='push'
|
||||
onChange={handleChange}/>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
const NotificationFormWrapper: React.FC<NotificationFormProps> = (props) => {
|
||||
return (
|
||||
<Loader>
|
||||
<NotificationForm {...props} />
|
||||
</Loader>
|
||||
);
|
||||
};
|
||||
|
||||
export { NotificationFormWrapper as NotificationForm };
|
@ -0,0 +1,58 @@
|
||||
import { Loader } from '../base/loader';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NotificationPreference } from '../../models/notification-preference';
|
||||
import { NotificationForm } from './notification-form';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import NotificationPreferencesAPI from '../../api/notification_preference';
|
||||
|
||||
interface NotificationsCategoryProps {
|
||||
onError: (message: string) => void,
|
||||
refreshSettings: () => void,
|
||||
categoryName: string,
|
||||
preferences: Array<NotificationPreference>
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the list of notifications
|
||||
*/
|
||||
const NotificationsCategory: React.FC<NotificationsCategoryProps> = ({ onError, categoryName, preferences, refreshSettings }) => {
|
||||
const { t } = useTranslation('logged');
|
||||
// Triggers a general update to enable all notifications for this category
|
||||
const enableAll = () => updateAll(true);
|
||||
|
||||
// Triggers a general update to disable all notifications for this category
|
||||
const disableAll = () => updateAll(false);
|
||||
|
||||
// Update all notifications for this category with a bulk_update.
|
||||
// This triggers a refresh of all the forms.
|
||||
const updateAll = async (value: boolean) => {
|
||||
const updatedPreferences: Array<NotificationPreference> = preferences.map(preference => {
|
||||
return { id: preference.id, notification_type: preference.notification_type, in_system: value, email: value };
|
||||
});
|
||||
await NotificationPreferencesAPI.bulk_update(updatedPreferences).catch(onError);
|
||||
refreshSettings();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="notifications-category">
|
||||
<h2 className="category-name">{`${t(`app.logged.notifications_category.${categoryName}`)}, ${t('app.logged.notifications_category.notify_me_when')}`}</h2>
|
||||
<div className="category-content">
|
||||
<div className="category-actions">
|
||||
<FabButton className="category-action category-action-left" onClick={enableAll}>{t('app.logged.notifications_category.enable_all')}</FabButton>
|
||||
<FabButton className="category-action" onClick={disableAll}>{t('app.logged.notifications_category.disable_all')}</FabButton>
|
||||
</div>
|
||||
{preferences.map(preference => <NotificationForm key={preference.notification_type} preference={preference} onError={onError}/>)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NotificationsCategoryWrapper: React.FC<NotificationsCategoryProps> = (props) => {
|
||||
return (
|
||||
<Loader>
|
||||
<NotificationsCategory {...props} />
|
||||
</Loader>
|
||||
);
|
||||
};
|
||||
|
||||
export { NotificationsCategoryWrapper as NotificationsCategory };
|
@ -4,8 +4,10 @@ import { react2angular } from 'react2angular';
|
||||
import { Loader } from '../base/loader';
|
||||
import { FabTabs } from '../base/fab-tabs';
|
||||
import { NotificationsList } from './notifications-list';
|
||||
import { NotificationsSettings } from './notifications-settings';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import MemberAPI from '../../api/member';
|
||||
import { UserRole } from '../../models/user';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -17,31 +19,30 @@ interface NotificationsCenterProps {
|
||||
* 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);
|
||||
const { t } = useTranslation('logged');
|
||||
const [role, setRole] = useState<UserRole>();
|
||||
|
||||
useEffect(() => {
|
||||
MemberAPI.current()
|
||||
.then(data => {
|
||||
if (data.role === 'admin') setIsAdmin(true);
|
||||
});
|
||||
.then(data => setRole(data.role))
|
||||
.catch(onError);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isAdmin && <FabTabs defaultTab='notifications-list' tabs={[
|
||||
{role === 'admin' && <FabTabs defaultTab='notifications_settings' tabs={[
|
||||
{
|
||||
id: 'notifications_settings',
|
||||
title: t('app.admin.notifications_center.notifications_settings'),
|
||||
content: 'to do notifications_settings'
|
||||
title: t('app.logged.notifications_center.notifications_settings'),
|
||||
content: <NotificationsSettings onError={onError}/>
|
||||
},
|
||||
{
|
||||
id: 'notifications-list',
|
||||
title: t('app.admin.notifications_center.notifications_list'),
|
||||
title: t('app.logged.notifications_center.notifications_list'),
|
||||
content: <NotificationsList onError={onError}/>
|
||||
}
|
||||
]} />}
|
||||
{!isAdmin && <NotificationsList onError={onError}/>}
|
||||
{role !== 'admin' && <NotificationsList onError={onError}/>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,86 @@
|
||||
import { Loader } from '../base/loader';
|
||||
import { useEffect, useState } from 'react';
|
||||
import NotificationPreferencesAPI from '../../api/notification_preference';
|
||||
import { NotificationPreference, NotificationCategoryNames, NotificationPreferencesByCategories } from '../../models/notification-preference';
|
||||
import { NotificationsCategory } from './notifications-category';
|
||||
import NotificationTypesAPI from '../../api/notification_types';
|
||||
|
||||
interface NotificationsSettingsProps {
|
||||
onError: (message: string) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the list of notifications
|
||||
*/
|
||||
const NotificationsSettings: React.FC<NotificationsSettingsProps> = ({ onError }) => {
|
||||
const [preferencesByCategories, setPreferencesCategories] = useState<NotificationPreferencesByCategories>({});
|
||||
|
||||
// From a default pattern of categories, and existing preferences and types retrieved from API,
|
||||
// this function builds an object with Notification Preferences sorted by categories.
|
||||
const fetchNotificationPreferences = async () => {
|
||||
let notificationPreferences: Array<NotificationPreference>;
|
||||
|
||||
await NotificationPreferencesAPI.index()
|
||||
.then(userNotificationPreferences => {
|
||||
notificationPreferences = userNotificationPreferences;
|
||||
})
|
||||
.catch(onError);
|
||||
|
||||
NotificationTypesAPI.index({ is_configurable: true })
|
||||
.then(notificationTypes => {
|
||||
// Initialize an object with every categories as keys
|
||||
const newPreferencesByCategories: NotificationPreferencesByCategories = {};
|
||||
for (const categoryName of NotificationCategoryNames) {
|
||||
newPreferencesByCategories[categoryName] = [];
|
||||
}
|
||||
|
||||
// For every notification type, we check if a notification preference already exists.
|
||||
// If there is none, we create one with default values.
|
||||
// Each Notification Preference is then placed in the right category.
|
||||
notificationTypes.forEach((notificationType) => {
|
||||
const existingPreference = notificationPreferences.find((notificationPreference) => {
|
||||
return notificationPreference.notification_type === notificationType.name;
|
||||
});
|
||||
newPreferencesByCategories[notificationType.category].push(
|
||||
existingPreference ||
|
||||
{
|
||||
notification_type: notificationType.name,
|
||||
in_system: true,
|
||||
email: true
|
||||
}
|
||||
);
|
||||
});
|
||||
setPreferencesCategories(newPreferencesByCategories);
|
||||
})
|
||||
.catch(onError);
|
||||
};
|
||||
|
||||
// Triggers the fetch Notification Preferences on component mount
|
||||
useEffect(() => {
|
||||
fetchNotificationPreferences();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="notifications-settings">
|
||||
{Object.entries(preferencesByCategories).map((notificationPreferencesCategory) => (
|
||||
<NotificationsCategory
|
||||
key={notificationPreferencesCategory[0]}
|
||||
categoryName={notificationPreferencesCategory[0]}
|
||||
preferences={notificationPreferencesCategory[1]}
|
||||
onError={onError}
|
||||
refreshSettings={fetchNotificationPreferences} />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NotificationsSettingsWrapper: React.FC<NotificationsSettingsProps> = (props) => {
|
||||
return (
|
||||
<Loader>
|
||||
<NotificationsSettings {...props} />
|
||||
</Loader>
|
||||
);
|
||||
};
|
||||
|
||||
export { NotificationsSettingsWrapper as NotificationsSettings };
|
@ -0,0 +1,24 @@
|
||||
export interface NotificationPreference {
|
||||
id: number,
|
||||
notification_type: string,
|
||||
email: boolean,
|
||||
in_system: boolean
|
||||
}
|
||||
|
||||
// This controls the order of the categories' display in the notification center
|
||||
export const NotificationCategoryNames = [
|
||||
'users_accounts',
|
||||
'proof_of_identity',
|
||||
'agenda',
|
||||
'subscriptions',
|
||||
'payments',
|
||||
'wallet',
|
||||
'shop',
|
||||
'projects',
|
||||
'accountings',
|
||||
'trainings'
|
||||
] as const;
|
||||
|
||||
export type NotificationCategoryName = typeof NotificationCategoryNames[number];
|
||||
|
||||
export type NotificationPreferencesByCategories = Record<NotificationCategoryName, Array<NotificationPreference>> | Record<never, never>
|
@ -75,7 +75,9 @@
|
||||
@import "modules/machines/machines-settings";
|
||||
@import "modules/machines/required-training-modal";
|
||||
@import "modules/notifications/notifications-list";
|
||||
@import "modules/notifications/notification-line";
|
||||
@import "modules/notifications/notification-inline";
|
||||
@import "modules/notifications/notifications-category";
|
||||
@import "modules/notifications/notification-form";
|
||||
@import "modules/payment-schedule/payment-schedule-dashboard";
|
||||
@import "modules/payment-schedule/payment-schedule-summary";
|
||||
@import "modules/payment-schedule/payment-schedules-list";
|
||||
|
@ -0,0 +1,32 @@
|
||||
.notification-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 2rem;
|
||||
border-bottom: 1px solid var(--gray-soft-dark);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.notification-type {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.form-action {
|
||||
width: 144px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
.notifications-category {
|
||||
max-width: 1200px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 32px;
|
||||
|
||||
.category-name {
|
||||
width: 288px;
|
||||
flex-shrink: 0;
|
||||
color: var(--gray-hard-darkest) !important;
|
||||
@include title-base;
|
||||
}
|
||||
|
||||
.category-content {
|
||||
max-width: 800px;
|
||||
flex-grow: 1;
|
||||
margin-top: 24px;
|
||||
padding: 20px;
|
||||
background-color: var(--gray-soft-light);
|
||||
border: 1px solid var(--gray-soft-dark);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.category-actions {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.category-action {
|
||||
background-color: #f5f5f5;
|
||||
font-size: 15px;
|
||||
text-transform: uppercase;
|
||||
border: none;
|
||||
margin-bottom: 3rem;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--gray-soft-dark);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
flex-direction: column;
|
||||
gap: 0px;
|
||||
.category-name {
|
||||
width: fit-content;
|
||||
}
|
||||
.category-content {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
}
|
||||
}
|
@ -4,4 +4,10 @@
|
||||
class NotificationPreference < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :notification_type
|
||||
|
||||
validates :notification_type_id, uniqueness: { scope: :user }
|
||||
|
||||
def notification_type
|
||||
NotificationType.find(notification_type_id).name
|
||||
end
|
||||
end
|
||||
|
12
app/policies/notification_preference_policy.rb
Normal file
12
app/policies/notification_preference_policy.rb
Normal file
@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Check the access policies for API::NotificationController
|
||||
class NotificationPreferencePolicy < ApplicationPolicy
|
||||
def update?
|
||||
user.admin?
|
||||
end
|
||||
|
||||
def bulk_update?
|
||||
user.admin?
|
||||
end
|
||||
end
|
@ -0,0 +1,3 @@
|
||||
json.array!(@notification_preferences) do |notification_preference|
|
||||
json.extract! notification_preference, :id, :notification_type, :in_system, :email
|
||||
end
|
@ -0,0 +1 @@
|
||||
json.extract! @notification_preference, :id, :notification_type, :in_system, :email
|
3
app/views/api/notification_types/index.json.jbuilder
Normal file
3
app/views/api/notification_types/index.json.jbuilder
Normal file
@ -0,0 +1,3 @@
|
||||
json.array!(@notification_types) do |notification_type|
|
||||
json.extract! notification_type, :id, :name, :category, :is_configurable
|
||||
end
|
@ -2412,6 +2412,3 @@ 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"
|
||||
|
@ -260,5 +260,63 @@ en:
|
||||
archives: "Archives"
|
||||
no_archived_notifications: "No archived notifications."
|
||||
load_the_next_notifications: "Load the next notifications..."
|
||||
notification_line:
|
||||
notification_inline:
|
||||
mark_as_read: "Mark as read"
|
||||
notifications_center:
|
||||
notifications_list: "All notifications"
|
||||
notifications_settings: "My notifications preferences"
|
||||
notifications_category:
|
||||
enable_all: "Enable all"
|
||||
disable_all: "Disable all"
|
||||
notify_me_when: "I wish to be notified when"
|
||||
users_accounts: "Concerning users notifications"
|
||||
proof_of_identity: "Concerning identity proofs notifications"
|
||||
agenda: "Concerning agenda notifications"
|
||||
subscriptions: "Concerning subscriptions notifications"
|
||||
payments: "Concerning payment schedules notifications"
|
||||
wallet: "Concerning wallet notifications"
|
||||
shop: "Concerning shop notifications"
|
||||
projects: "Concerning projects notifications"
|
||||
accountings: "Concerning accounting notifications"
|
||||
trainings: "Concerning trainings notifications"
|
||||
app_management: "Concerning app management notifications"
|
||||
notification_form:
|
||||
notify_admin_when_user_is_created: "A user account has been created"
|
||||
notify_admin_when_user_is_imported: "A user account has been imported"
|
||||
notify_admin_profile_complete: "An imported account has completed its profile"
|
||||
notify_admin_user_merged: "An imported account has been merged with an existing account"
|
||||
notify_admins_role_update: "The role of a user has changed"
|
||||
notify_admin_import_complete: "An import is done"
|
||||
notify_admin_user_group_changed: "A group has changed"
|
||||
notify_admin_user_proof_of_identity_refusal: "A proof of identity has been rejected"
|
||||
notify_admin_user_proof_of_identity_files_created: "A user has uploaded a proof of identity"
|
||||
notify_admin_user_proof_of_identity_files_updated: "A user has updated a proof of identity"
|
||||
notify_admin_member_create_reservation: "A member creates a reservation"
|
||||
notify_admin_slot_is_modified: "A reservation slot has been modified"
|
||||
notify_admin_slot_is_canceled: "A reservation has been cancelled"
|
||||
notify_admin_subscribed_plan: "A subscription has been purchased"
|
||||
notify_admin_subscription_will_expire_in_7_days: "A member subscription expires in 7 days"
|
||||
notify_admin_subscription_is_expired: "A member subscription has expired"
|
||||
notify_admin_subscription_extended: "A subscription has been extended"
|
||||
notify_admin_subscription_canceled: "A member subscription has been cancelled"
|
||||
notify_admin_payment_schedule_failed: "Card debit failure"
|
||||
notify_admin_payment_schedule_check_deadline: "A payment deadline is soon"
|
||||
notify_admin_payment_schedule_transfer_deadline: "A bank direct debit has to be confirmed"
|
||||
notify_admin_payment_schedule_error: "An error occurred for the card debit for a schedule"
|
||||
notify_admin_gateway_canceled: "You must confirm a bank direct debit for for a schedule"
|
||||
notify_admin_refund_created: "A refund has been created"
|
||||
notify_admin_user_wallet_is_credited: "The wallet of an user has been credited"
|
||||
notify_user_order_is_ready: "Your command is ready"
|
||||
notify_user_order_is_canceled: "Your command was canceled"
|
||||
notify_user_order_is_refunded: "Your command was refunded"
|
||||
notify_admin_low_stock_threshold: "The stock is low"
|
||||
notify_admin_when_project_published: "A project has been published"
|
||||
notify_admin_abuse_reported: "An abusive content has been reported"
|
||||
notify_admin_close_period_reminder: "An accounting period has to be closed soon"
|
||||
notify_admin_archive_complete: "An archive is completed"
|
||||
notify_admin_training_auto_cancelled: "A training was automatically cancelled"
|
||||
notify_admin_export_complete: "An export is completed"
|
||||
notify_user_when_invoice_ready: "An invoice is available"
|
||||
notify_admin_payment_schedule_gateway_canceled: "A payment schedule has been canceled by the payment gateway"
|
||||
notify_project_collaborator_to_valid: "You are invited to collaborate on the project"
|
||||
notify_project_author_when_collaborator_valid: "A collaborator has accepted your invitation to join your project"
|
||||
|
@ -72,6 +72,10 @@ Rails.application.routes.draw do
|
||||
get 'polling', action: 'polling', on: :collection
|
||||
get 'last_unread', action: 'last_unread', on: :collection
|
||||
end
|
||||
resources :notification_types, only: %i[index]
|
||||
resources :notification_preferences, only: %i[index update], param: :notification_type do
|
||||
patch '/bulk_update', action: 'bulk_update', on: :collection
|
||||
end
|
||||
resources :wallet, only: [] do
|
||||
get '/by_user/:user_id', action: 'by_user', on: :collection
|
||||
get :transactions, on: :member
|
||||
|
@ -5,11 +5,13 @@
|
||||
class CreateNotificationPreferences < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :notification_preferences do |t|
|
||||
t.references :user, index: true, foreign_key: true, null: false
|
||||
t.references :user, index: false, foreign_key: true, null: false
|
||||
t.references :notification_type, index: true, foreign_key: true, null: false
|
||||
t.boolean :in_system, default: true
|
||||
t.boolean :email, default: true
|
||||
|
||||
t.index %i[user_id notification_type_id], unique: true, name: :index_notification_preferences_on_user_and_notification_type
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user