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

Merge branch 'settings' into dev

This commit is contained in:
Sylvain 2020-06-15 17:53:17 +02:00
commit 68a99718c8
290 changed files with 3267 additions and 1679 deletions

View File

@ -7,7 +7,8 @@
"Application": true,
"angular": true,
"Fablab": true,
"moment": true,
}
"moment": true
},
"plugins": ["lint-erb"]
}

View File

@ -1,5 +1,17 @@
# Changelog Fab-manager
- Ability to configure most of the settings from the admin's UI
- Ability to lock some settings from the environment
- Improved display of the icons alerting about an outdated version
- Improved mime-type checking (back & front)
- Updated CarrierWave to 2.1.0
- Updated redis to v6, with alpine image
- Updated Sidekiq to 6.0.7
- Fix a bug: managers do not see the name of the user who reserved a slot
- Fix a bug: OpenAPI documentation is not available
- [TODO DEPLOY] `rails fablab:setup:env_to_db`
- [TODO DEPLOY] `\curl -sSL https://raw.githubusercontent.com/sleede/fab-manager/master/scripts/redis-upgrade.sh | bash`
## v4.4.6 2020 June 01
- Fix a security issue: updated kaminari from 1.2.0 to 1.2.1 to fix [CVE-2020-11082](https://nvd.nist.gov/vuln/detail/CVE-2020-11082)

View File

@ -96,11 +96,10 @@ gem 'friendly_id', '~> 5.1.0'
gem 'aasm'
# Background job processing
gem 'redis-namespace'
gem 'sidekiq', '>= 3.4.2'
gem 'sinatra', require: false
gem 'sidekiq', '>= 6.0.7'
# Recurring jobs for Sidekiq
gem 'sidekiq-cron'
gem 'sidekiq-unique-jobs', '~> 6.0.22'
gem 'stripe', '5.1.1'

View File

@ -91,11 +91,13 @@ GEM
sassc (>= 2.0.0)
builder (3.2.4)
camertron-eprun (1.1.1)
carrierwave (0.10.0)
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
json (>= 1.7)
mime-types (>= 1.16)
carrierwave (2.1.0)
activemodel (>= 5.0.0)
activesupport (>= 5.0.0)
addressable (~> 2.6)
image_processing (~> 1.1)
mimemagic (>= 0.3.0)
mini_mime (>= 0.1.3)
caxlsx (3.0.1)
htmlentities (~> 4.3, >= 4.3.4)
mimemagic (~> 0.3)
@ -114,7 +116,7 @@ GEM
sass-rails (< 5.1)
sprockets (< 4.0)
concurrent-ruby (1.1.6)
connection_pool (2.2.2)
connection_pool (2.2.3)
coveralls (0.8.23)
json (>= 1.8, < 3)
simplecov (~> 0.16.1)
@ -192,6 +194,9 @@ GEM
ice_cube (~> 0.16)
ice_cube (0.16.3)
ice_nine (0.11.2)
image_processing (1.11.0)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
jaro_winkler (1.5.4)
jbuilder (2.10.0)
activesupport (>= 5.0.0)
@ -230,7 +235,7 @@ GEM
method_source (1.0.0)
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2019.1009)
mime-types-data (3.2020.0512)
mimemagic (0.3.5)
mini_magick (4.10.1)
mini_mime (1.0.2)
@ -245,8 +250,6 @@ GEM
multi_json (1.14.1)
multi_xml (0.6.0)
multipart-post (2.1.1)
mustermann (1.1.1)
ruby2_keywords (~> 0.0.1)
nio4r (2.5.2)
nokogiri (1.10.9)
mini_portile2 (~> 2.4.0)
@ -342,9 +345,7 @@ GEM
recurrence (1.3.0)
activesupport
i18n
redis (4.1.3)
redis-namespace (1.6.0)
redis (>= 3.0.4)
redis (4.1.4)
repost (0.3.2)
responders (2.4.1)
actionpack (>= 4.2.0, < 6.0)
@ -360,7 +361,8 @@ GEM
unicode-display_width (~> 1.4.0)
ruby-progressbar (1.10.1)
ruby-rc4 (0.1.5)
ruby2_keywords (0.0.2)
ruby-vips (2.0.17)
ffi (~> 1.9)
rubyzip (1.3.0)
safe_yaml (1.0.5)
sass (3.4.25)
@ -379,24 +381,23 @@ GEM
activerecord (>= 4)
activesupport (>= 4)
sha3 (1.0.1)
sidekiq (5.2.7)
connection_pool (~> 2.2, >= 2.2.2)
rack (>= 1.5.0)
rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5)
sidekiq (6.0.7)
connection_pool (>= 2.2.2)
rack (~> 2.0)
rack-protection (>= 2.0.0)
redis (>= 4.1.0)
sidekiq-cron (1.1.0)
fugit (~> 1.1)
sidekiq (>= 4.2.1)
sidekiq-unique-jobs (6.0.22)
concurrent-ruby (~> 1.0, >= 1.0.5)
sidekiq (>= 4.0, < 7.0)
thor (~> 0)
simplecov (0.16.1)
docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
sinatra (2.0.8.1)
mustermann (~> 1.0)
rack (~> 2.0)
rack-protection (= 2.0.8.1)
tilt (~> 2.0)
spring (2.0.2)
activesupport (>= 4.2)
spring-watcher-listen (2.0.1)
@ -415,7 +416,7 @@ GEM
ffi
term-ansicolor (1.7.1)
tins (~> 1.0)
thor (1.0.1)
thor (0.20.3)
thread_safe (0.3.6)
tilt (2.0.10)
tins (1.24.1)
@ -509,7 +510,6 @@ DEPENDENCIES
rails_12factor
rb-readline
recurrence
redis-namespace
repost
responders (~> 2.0)
rolify
@ -519,9 +519,9 @@ DEPENDENCIES
sdoc (~> 0.4.0)
seed_dump
sha3
sidekiq (>= 3.4.2)
sidekiq (>= 6.0.7)
sidekiq-cron
sinatra
sidekiq-unique-jobs (~> 6.0.22)
spring
spring-watcher-listen (~> 2.0.0)
stripe (= 5.1.1)

View File

@ -27,9 +27,9 @@ Fab-manager is the Fab Lab management solution. It provides a comprehensive, web
Fab-manager is a Ruby on Rails / AngularJS web application that runs on the following software:
- Ubuntu LTS 14.04+ / Debian 8+
- Ruby 2.3
- Redis 2.8.4+
- Sidekiq 3.3.4+
- Ruby 2.6
- Redis 6
- Sidekiq 6
- Elasticsearch 5.6
- PostgreSQL 9.6
@ -71,9 +71,8 @@ The deal is fair, you share your projects and as reward you benefits from projec
If you want to try it, you can visit [this Fab-manager](https://fablab.lacasemate.fr/#!/projects) and see projects from different Fab-managers.
To start using this awesome feature, there are a few steps:
- send a mail to **contact@fab-manager.com** asking for your Open Projects client's credentials and giving them the name of your Fab-manager, they will give you an `OPENLAB_APP_ID` and an `OPENLAB_APP_SECRET`
- fill in the value of the keys in your environment file
- start your Fab-manager app
- send a mail to **contact@fab-manager.com** asking for your Open Projects client's credentials and giving them the name and the URL of your Fab-manager, they will give you an `App ID` and a `secret`
- fill in the value of the keys in Admin > Projects > Settings > Projects sharing
- export your projects to open-projects (if you already have projects created on your Fab-manager, unless you can skip that part) executing this command: `bundle exec rake fablab:openlab:bulk_export`
**IMPORTANT: please run your server in production mode.**
@ -99,9 +98,9 @@ You can see an example on the [repo of navinum gamification plugin](https://gith
## Single Sign-On
Fab-manager can be connected to a [Single Sign-On](https://en.wikipedia.org/wiki/Single_sign-on) server which will provide its own authentication for the platform's users.
Currently OAuth 2 is the only supported protocol for SSO authentication.
Currently, OAuth 2 is the only supported protocol for SSO authentication.
For an example of how to use configure a SSO in Fab-manager, please read [sso_with_github.md](doc/sso_with_github.md).
For an example of how to use configure an SSO in Fab-manager, please read [sso_with_github.md](doc/sso_with_github.md).
<a name="known-issues"></a>
## Known issues

View File

@ -17,7 +17,7 @@ Application.Directives = angular.module('application.directives', []);
angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ui.router', 'ui.bootstrap',
'ngUpload', 'duScroll', 'application.filters', 'application.services', 'application.directives',
'frapontillo.bootstrap-switch', 'application.constants', 'application.controllers', 'application.router',
'ui.select', 'ui.calendar', 'angularMoment', 'Devise', 'DeviseModal', 'angular-growl', 'xeditable',
'ui.select', 'ui.calendar', 'angularMoment', 'Devise', 'angular-growl', 'xeditable',
'checklist-model', 'unsavedChanges', 'angular-loading-bar', 'ngTouch', 'angular-google-analytics',
'angularUtils.directives.dirDisqus', 'summernote', 'elasticsearch', 'angular-medium-editor', 'naif.base64',
'minicolors', 'pascalprecht.translate', 'ngFitText', 'ngAside', 'ngCapsLock', 'vcRecaptcha', 'ui.codemirror',
@ -28,10 +28,10 @@ angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ui.rout
// first we check the user acceptance
const cookiesConsent = document.cookie.replace(/(?:(?:^|.*;\s*)fab-manager-cookies-consent\s*=\s*([^;]*).*$)|^.*$/, '$1');
if (cookiesConsent === 'accept') {
AnalyticsProvider.setAccount(Fablab.gaId);
AnalyticsProvider.setAccount(Fablab.trackingId);
// track all routes (or not)
AnalyticsProvider.trackPages(true);
AnalyticsProvider.setDomainName(Fablab.defaultHost);
AnalyticsProvider.setDomainName(Fablab.baseHostUrl);
AnalyticsProvider.useAnalytics(true);
AnalyticsProvider.setPageEvent('$stateChangeSuccess');
} else {
@ -81,25 +81,6 @@ angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ui.rout
$state.prevParams = fromParams;
});
// Global config: if true, the whole 'Plans & Subscriptions' feature will be disabled in the application
$rootScope.fablabWithoutPlans = Fablab.withoutPlans;
// Global config: it true, the whole 'Spaces' features will be disabled in the application
$rootScope.fablabWithoutSpaces = Fablab.withoutSpaces;
// Global config: if true, all payments will be disabled in the application for the members (only admins will be able to proceed reservations)
$rootScope.fablabWithoutOnlinePayment = Fablab.withoutOnlinePayment;
// Global config: if true, no invoices will be generated
$rootScope.fablabWithoutInvoices = Fablab.withoutInvoices;
// Global config: if true, the phone number is required to create an account
$rootScope.phoneRequired = Fablab.phoneRequired;
// Global config: if true, the events are shown in the admin calendar
$rootScope.eventsInCalendar = Fablab.eventsInCalendar;
// Global config: machine/space slot duration
$rootScope.slotDuration = Fablab.slotDuration;
// Global config: if true, user must confirm his email to sign in
$rootScope.userConfirmationNeededToSignIn = Fablab.userConfirmationNeededToSignIn;
// Global config: if true, wallet will be disable
$rootScope.fablabWithoutWallet = Fablab.fablabWithoutWallet;
// Global function to allow the user to navigate to the previous screen (ie. $state).
// If no previous $state were recorded, navigate to the home page
$rootScope.backPrevLocation = function (event) {

View File

@ -18,8 +18,8 @@
* Controller used in the calendar management page
*/
Application.Controllers.controller('AdminCalendarController', ['$scope', '$state', '$uibModal', 'moment', 'AuthService', 'Availability', 'Slot', 'Setting', 'Export', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', 'plansPromise', 'groupsPromise', '_t', 'uiCalendarConfig', 'CalendarConfig', 'Member', 'uiTourService',
function ($scope, $state, $uibModal, moment, AuthService, Availability, Slot, Setting, Export, growl, dialogs, bookingWindowStart, bookingWindowEnd, machinesPromise, plansPromise, groupsPromise, _t, uiCalendarConfig, CalendarConfig, Member, uiTourService) {
Application.Controllers.controller('AdminCalendarController', ['$scope', '$state', '$uibModal', 'moment', 'AuthService', 'Availability', 'Slot', 'Setting', 'Export', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', 'plansPromise', 'groupsPromise', 'settingsPromise', '_t', 'uiCalendarConfig', 'CalendarConfig', 'Member', 'uiTourService',
function ($scope, $state, $uibModal, moment, AuthService, Availability, Slot, Setting, Export, growl, dialogs, bookingWindowStart, bookingWindowEnd, machinesPromise, plansPromise, groupsPromise, settingsPromise, _t, uiCalendarConfig, CalendarConfig, Member, uiTourService) {
/* PRIVATE STATIC CONSTANTS */
// The calendar is divided in slots of 30 minutes
@ -29,7 +29,7 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
const BOOKING_SNAP = '00:30:00';
// We do not allow the creation of slots that are not a multiple of 60 minutes
const SLOT_MULTIPLE = Fablab.slotDuration;
const SLOT_MULTIPLE = parseInt(settingsPromise.slot_duration, 10);
/* PUBLIC SCOPE */
@ -42,6 +42,9 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
// corresponding fullCalendar item in the DOM
$scope.availabilityDom = null;
// Should we show the scheduled events in the calendar?
$scope.eventsInCalendar = (settingsPromise.events_in_calendar === 'true');
// bind the availabilities slots with full-Calendar events
$scope.eventSources = [];
$scope.eventSources.push({
@ -349,7 +352,7 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
}
});
// if the user has never seen the tour, show him now
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('calendar') < 0) {
if (settingsPromise.feature_tour_display !== 'manual' && $scope.currentUser.profile.tours.indexOf('calendar') < 0) {
uitour.start();
}
}
@ -429,7 +432,8 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
spacesPromise: ['Space', function (Space) { return Space.query().$promise; }],
tagsPromise: ['Tag', function (Tag) { return Tag.query().$promise; }],
plansPromise: ['Plan', function (Plan) { return Plan.query().$promise; }],
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }]
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }],
slotDurationPromise: ['Setting', function (Setting) { return Setting.get({ name: 'slot_duration' }).$promise; }]
} });
// when the modal is closed, we send the slot to the server for saving
modalInstance.result.then(
@ -531,8 +535,8 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
/**
* Controller used in the slot creation modal window
*/
Application.Controllers.controller('CreateEventModalController', ['$scope', '$uibModalInstance', '$sce', 'moment', 'start', 'end', 'slots', 'machinesPromise', 'Availability', 'trainingsPromise', 'spacesPromise', 'tagsPromise', 'plansPromise', 'groupsPromise', 'growl', '_t',
function ($scope, $uibModalInstance, $sce, moment, start, end, slots, machinesPromise, Availability, trainingsPromise, spacesPromise, tagsPromise, plansPromise, groupsPromise, growl, _t) {
Application.Controllers.controller('CreateEventModalController', ['$scope', '$uibModalInstance', '$sce', 'moment', 'start', 'end', 'slots', 'machinesPromise', 'Availability', 'trainingsPromise', 'spacesPromise', 'tagsPromise', 'plansPromise', 'groupsPromise', 'slotDurationPromise', 'growl', '_t',
function ($scope, $uibModalInstance, $sce, moment, start, end, slots, machinesPromise, Availability, trainingsPromise, spacesPromise, tagsPromise, plansPromise, groupsPromise, slotDurationPromise, growl, _t) {
// $uibModal parameter
$scope.start = start;
@ -595,7 +599,7 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
period: 'week',
nb_periods: 1,
end_date: undefined, // recurrence end
slot_duration: Fablab.slotDuration
slot_duration: parseInt(slotDurationPromise.setting.value, 10)
};
// recurrent slots

View File

@ -153,8 +153,8 @@ class EventsController {
/**
* Controller used in the events listing page (admin view)
*/
Application.Controllers.controller('AdminEventsController', ['$scope', '$state', 'dialogs', '$uibModal', 'growl', 'AuthService', 'Event', 'Category', 'EventTheme', 'AgeRange', 'PriceCategory', 'eventsPromise', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'priceCategoriesPromise', '_t', 'Member', 'uiTourService',
function ($scope, $state, dialogs, $uibModal, growl, AuthService, Event, Category, EventTheme, AgeRange, PriceCategory, eventsPromise, categoriesPromise, themesPromise, ageRangesPromise, priceCategoriesPromise, _t, Member, uiTourService) {
Application.Controllers.controller('AdminEventsController', ['$scope', '$state', 'dialogs', '$uibModal', 'growl', 'AuthService', 'Event', 'Category', 'EventTheme', 'AgeRange', 'PriceCategory', 'eventsPromise', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'priceCategoriesPromise', '_t', 'Member', 'uiTourService', 'settingsPromise',
function ($scope, $state, dialogs, $uibModal, growl, AuthService, Event, Category, EventTheme, AgeRange, PriceCategory, eventsPromise, categoriesPromise, themesPromise, ageRangesPromise, priceCategoriesPromise, _t, Member, uiTourService, settingsPromise) {
/* PUBLIC SCOPE */
// By default, the pagination mode is activated to limit the page size
@ -465,7 +465,7 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state',
}
});
// if the user has never seen the tour, show him now
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('events') < 0) {
if (settingsPromise.feature_tour_display !== 'manual' && $scope.currentUser.profile.tours.indexOf('events') < 0) {
uitour.start();
}
}

View File

@ -17,18 +17,21 @@
/**
* Controller used in the admin invoices listing page
*/
Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'Invoice', 'AccountingPeriod', 'AuthService', 'invoices', 'closedPeriods', '$uibModal', 'growl', '$filter', 'Setting', 'settings', '_t', 'Member', 'uiTourService',
function ($scope, $state, Invoice, AccountingPeriod, AuthService, invoices, closedPeriods, $uibModal, growl, $filter, Setting, settings, _t, Member, uiTourService) {
Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'Invoice', 'AccountingPeriod', 'AuthService', 'invoices', 'closedPeriods', '$uibModal', 'growl', '$filter', 'Setting', 'settings', 'stripeSecretKey', '_t', 'Member', 'uiTourService', 'Payment', 'onlinePaymentStatus',
function ($scope, $state, Invoice, AccountingPeriod, AuthService, invoices, closedPeriods, $uibModal, growl, $filter, Setting, settings, stripeSecretKey, _t, Member, uiTourService, Payment, onlinePaymentStatus) {
/* PRIVATE STATIC CONSTANTS */
// number of invoices loaded each time we click on 'load more...'
const INVOICES_PER_PAGE = 20;
// fake stripe secret key
const STRIPE_SK_HIDDEN = 'sk_test_hidden-hidden-hidden-hid';
/* PUBLIC SCOPE */
// default active tab
$scope.tabs = {
active: Fablab.withoutInvoices ? 1 : 0
active: settings.invoicing_module === 'true' ? 0 : 1
};
// List of all invoices
@ -50,6 +53,14 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
// Default invoices ordering/sorting
$scope.orderInvoice = '-reference';
// Invoice PDF filename settings (and example)
$scope.file = {
prefix: settings.invoice_prefix,
nextId: 40,
date: moment().format('DDMMYYYY'),
templateUrl: 'editPrefix.html'
}
// Invoices parameters
$scope.invoice = {
logo: null,
@ -169,6 +180,15 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
}
};
// all settings
$scope.allSettings = settings;
// is the stripe private set?
$scope.stripeSecretKey = (stripeSecretKey.isPresent ? STRIPE_SK_HIDDEN : '');
// has any online payment been already made?
$scope.onlinePaymentStatus = onlinePaymentStatus.status;
// Placeholding date for the invoice creation
$scope.today = moment();
@ -465,6 +485,38 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
});
};
/**
* Open a modal dialog allowing the user to edit the prefix of the invoice file name
*/
$scope.openEditPrefix = function () {
const modalInstance = $uibModal.open({
animation: true,
templateUrl: $scope.file.templateUrl,
size: 'lg',
resolve: {
model () { return $scope.file.prefix;}
},
controller: ['$scope', '$uibModalInstance', 'model', function ($scope, $uibModalInstance, model) {
$scope.model = model;
$scope.ok = function () { $uibModalInstance.close($scope.model); };
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
}]
});
return modalInstance.result.then(function (model) {
Setting.update({ name: 'invoice_prefix' }, { value: model }, function (data) {
$scope.file.prefix = model;
return growl.success(_t('app.admin.invoices.prefix_successfully_saved'));
}
, function (error) {
if (error.status === 304) return;
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_prefix'));
console.error(error);
});
});
};
/**
* Callback to save the value of the text zone when editing is done
*/
@ -569,7 +621,7 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
{ settings: Object.values($scope.settings) },
function () { growl.success(_t('app.admin.invoices.codes_customization_success')); },
function (error) {
growl.error('unexpected_error_occurred');
growl.error('app.admin.invoices.unexpected_error_occurred');
console.error(error);
}
);
@ -584,6 +636,42 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
return `${invoice.operator.first_name} ${invoice.operator.last_name}`;
}
/**
* Open a modal dialog which ask for the stripe keys
* @param onlinePaymentModule {{name: String, value: String}} setting that defines the next status of the online payment module
* @return {boolean} false if the keys were not provided
*/
$scope.requireStripeKeys = function(onlinePaymentModule) {
// if the online payment is about to be disabled, accept the change without any further question
if (onlinePaymentModule.value === false) return true;
// otherwise, open a modal to ask for the stripe keys
const modalInstance = $uibModal.open({
templateUrl: 'stripeKeys.html',
controller: 'StripeKeysModalController',
resolve: {
stripeKeys: ['Setting', function (Setting) { return Setting.query({ names: "['stripe_public_key', 'stripe_secret_key']" }).$promise; }]
}
});
modalInstance.result.then(function (success) {
if (success) {
Setting.get({ name: 'stripe_public_key' }, function (res) {
$scope.allSettings.stripe_public_key = res.setting.value;
})
Setting.isPresent({ name: 'stripe_secret_key' }, function (res) {
$scope.stripeSecretKey = (res.isPresent ? STRIPE_SK_HIDDEN : '');
})
Payment.onlinePaymentStatus(function (res) {
$scope.onlinePaymentStatus = res.status;
});
}
})
// return the promise
return modalInstance.result;
}
/**
* Setup the feature-tour for the admin/invoices page.
* This is intended as a contextual help (when pressing F1)
@ -612,7 +700,7 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
orphan: true
});
}
if (!Fablab.withoutInvoices && $scope.invoices.length > 0) {
if (settings.invoicing_module === 'true' && $scope.invoices.length > 0) {
uitour.createStep({
selector: '.invoices-management .invoices-list',
stepId: 'list',
@ -671,10 +759,19 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
content: _t('app.admin.tour.invoices.export.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.invoices-management .payment-settings',
stepId: 'payment',
order: 8,
title: _t('app.admin.tour.invoices.payment.title'),
content: _t('app.admin.tour.invoices.payment.content'),
placement: 'bottom',
popupClass: 'shift-left-50'
});
uitour.createStep({
selector: '.heading .close-accounting-periods-button',
stepId: 'periods',
order: 8,
order: 9,
title: _t('app.admin.tour.invoices.periods.title'),
content: _t('app.admin.tour.invoices.periods.content'),
placement: 'bottom',
@ -684,7 +781,7 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
uitour.createStep({
selector: 'body',
stepId: 'conclusion',
order: 9,
order: 10,
title: _t('app.admin.tour.conclusion.title'),
content: _t('app.admin.tour.conclusion.content'),
placement: 'bottom',
@ -701,6 +798,9 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
if (nextStep.stepId === 'codes' || nextStep.stepId === 'export') {
$scope.tabs.active = 2;
}
if (nextStep.stepId === 'payment') {
$scope.tabs.active = 3;
}
});
// on tour end, save the status in database
uitour.on('ended', function () {
@ -711,7 +811,7 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
}
});
// if the user has never seen the tour, show him now
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('invoices') < 0) {
if (settings.feature_tour_display !== 'manual' && $scope.currentUser.profile.tours.indexOf('invoices') < 0) {
uitour.start();
}
}
@ -1209,3 +1309,131 @@ Application.Controllers.controller('AccountingExportModalController', ['$scope',
// !!! MUST BE CALLED AT THE END of the controller
return initialize();
}]);
/**
* Controller used in the modal window allowing an admin to close an accounting period
*/
Application.Controllers.controller('StripeKeysModalController', ['$scope', '$uibModalInstance', '$http', '$httpParamSerializerJQLike', 'stripeKeys', 'Setting', 'growl', '_t',
function ($scope, $uibModalInstance, $http, $httpParamSerializerJQLike, stripeKeys, Setting, growl, _t) {
/* PUBLIC SCOPE */
// public key
$scope.publicKey = stripeKeys.stripe_public_key;
// test status of the public key
$scope.publicKeyStatus = undefined;
// secret key
$scope.secretKey = stripeKeys.stripe_secret_key;
// test status of the secret key
$scope.secretKeyStatus = undefined;
/**
* Trigger the test of the secret key and set the result in $scope.secretKeyStatus
*/
$scope.testSecretKey = function () {
if (!$scope.secretKey.match(/^sk_/)) {
$scope.secretKeyStatus = false;
return;
}
$http({
method: 'GET',
url: 'https://api.stripe.com/v1/charges',
headers: {
Authorization: `Bearer ${$scope.secretKey}`,
}
}).then(function () {
$scope.secretKeyStatus = true;
}, function (err) {
if (err.status === 401) $scope.secretKeyStatus = false;
});
}
/**
* Trigger the test of the secret key and set the result in $scope.secretKeyStatus
*/
$scope.testPublicKey = function () {
if (!$scope.publicKey.match(/^pk_/)) {
$scope.publicKeyStatus = false;
return;
}
const today = new Date();
$http({
method: 'POST',
url: 'https://api.stripe.com/v1/tokens',
headers: {
Authorization: `Bearer ${$scope.publicKey}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
data: $httpParamSerializerJQLike({
'card[number]': 4242424242424242,
'card[cvc]': 123,
'card[exp_month]': 12,
'card[exp_year]': today.getFullYear().toString().substring(2),
})
}).then(function () {
$scope.publicKeyStatus = true;
}, function (err) {
if (err.status === 401) $scope.publicKeyStatus = false;
});
}
/**
* Validate the keys
*/
$scope.ok = function () {
if ($scope.secretKeyStatus && $scope.publicKeyStatus) {
Setting.bulkUpdate(
{ settings: [
{
name: 'stripe_public_key',
value: $scope.publicKey
},
{
name: 'stripe_secret_key',
value: $scope.secretKey
}
] },
function () {
growl.success(_t('app.admin.invoices.payment.stripe_keys_saved'));
$uibModalInstance.close(true);
},
function (error) {
growl.error('app.admin.invoices.payment.error_saving_stripe_keys');
console.error(error);
}
);
} else {
growl.error(_t('app.admin.invoices.payment.error_check_keys'))
}
};
/**
* Just dismiss the modal window
*/
$scope.cancel = function () {
$uibModalInstance.dismiss('cancel');
};
/* PRIVATE SCOPE */
/**
* Kind of constructor: these actions will be realized first when the controller is loaded
*/
const initialize = function () {
if (stripeKeys.stripe_public_key) {
$scope.testPublicKey();
}
if (stripeKeys.stripe_secret_key) {
$scope.testSecretKey();
}
};
// !!! MUST BE CALLED AT THE END of the controller!
return initialize();
}
]);

View File

@ -126,8 +126,8 @@ class MembersController {
/**
* Controller used in the members/groups management page
*/
Application.Controllers.controller('AdminMembersController', ['$scope', '$sce', '$uibModal', 'membersPromise', 'adminsPromise', 'partnersPromise', 'managersPromise', 'growl', 'Admin', 'AuthService', 'dialogs', '_t', 'Member', 'Export', 'User', 'uiTourService',
function ($scope, $sce, $uibModal, membersPromise, adminsPromise, partnersPromise, managersPromise, growl, Admin, AuthService, dialogs, _t, Member, Export, User, uiTourService) {
Application.Controllers.controller('AdminMembersController', ['$scope', '$sce', '$uibModal', 'membersPromise', 'adminsPromise', 'partnersPromise', 'managersPromise', 'growl', 'Admin', 'AuthService', 'dialogs', '_t', 'Member', 'Export', 'User', 'uiTourService', 'settingsPromise',
function ($scope, $sce, $uibModal, membersPromise, adminsPromise, partnersPromise, managersPromise, growl, Admin, AuthService, dialogs, _t, Member, Export, User, uiTourService, settingsPromise) {
/* PRIVATE STATIC CONSTANTS */
// number of users loaded each time we click on 'load more...'
@ -567,7 +567,7 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
}
});
// if the user has never seen the tour, show him now
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('members') < 0) {
if (settingsPromise.feature_tour_display !== 'manual' && $scope.currentUser.profile.tours.indexOf('members') < 0) {
uitour.start();
}
}
@ -641,8 +641,8 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
/**
* Controller used in the member edition page
*/
Application.Controllers.controller('EditMemberController', ['$scope', '$state', '$stateParams', 'Member', 'Training', 'dialogs', 'growl', 'Group', 'Subscription', 'CSRF', 'memberPromise', 'tagsPromise', '$uibModal', 'Plan', '$filter', '_t', 'walletPromise', 'transactionsPromise', 'activeProviderPromise', 'Wallet',
function ($scope, $state, $stateParams, Member, Training, dialogs, growl, Group, Subscription, CSRF, memberPromise, tagsPromise, $uibModal, Plan, $filter, _t, walletPromise, transactionsPromise, activeProviderPromise, Wallet) {
Application.Controllers.controller('EditMemberController', ['$scope', '$state', '$stateParams', 'Member', 'Training', 'dialogs', 'growl', 'Group', 'Subscription', 'CSRF', 'memberPromise', 'tagsPromise', '$uibModal', 'Plan', '$filter', '_t', 'walletPromise', 'transactionsPromise', 'activeProviderPromise', 'Wallet', 'phoneRequiredPromise',
function ($scope, $state, $stateParams, Member, Training, dialogs, growl, Group, Subscription, CSRF, memberPromise, tagsPromise, $uibModal, Plan, $filter, _t, walletPromise, transactionsPromise, activeProviderPromise, Wallet, phoneRequiredPromise) {
/* PUBLIC SCOPE */
// API URL where the form will be posted
@ -660,6 +660,9 @@ Application.Controllers.controller('EditMemberController', ['$scope', '$state',
// Should the password be modified?
$scope.password = { change: false };
// is the phone number required in _member_form?
$scope.phoneRequired = (phoneRequiredPromise.setting.value === 'true');
// the user subscription
if (($scope.user.subscribed_plan != null) && ($scope.user.subscription != null)) {
$scope.subscription = $scope.user.subscription;
@ -948,8 +951,8 @@ Application.Controllers.controller('EditMemberController', ['$scope', '$state',
/**
* Controller used in the member's creation page (admin view)
*/
Application.Controllers.controller('NewMemberController', ['$scope', '$state', '$stateParams', 'Member', 'Training', 'Group', 'CSRF',
function ($scope, $state, $stateParams, Member, Training, Group, CSRF) {
Application.Controllers.controller('NewMemberController', ['$scope', '$state', '$stateParams', 'Member', 'Training', 'Group', 'CSRF', 'phoneRequiredPromise',
function ($scope, $state, $stateParams, Member, Training, Group, CSRF, phoneRequiredPromise) {
CSRF.setMetaTags();
/* PUBLIC SCOPE */
@ -963,6 +966,9 @@ Application.Controllers.controller('NewMemberController', ['$scope', '$state', '
// Should the password be set manually or generated?
$scope.password = { change: false };
// is the phone number required in _member_form?
$scope.phoneRequired = (phoneRequiredPromise.setting.value === 'true');
// Default member's profile parameters
$scope.user = {
plan_interval: '',

View File

@ -10,8 +10,8 @@
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
Application.Controllers.controller('OpenAPIClientsController', ['$scope', 'clientsPromise', 'growl', 'OpenAPIClient', 'dialogs', '_t', 'Member', 'uiTourService',
function ($scope, clientsPromise, growl, OpenAPIClient, dialogs, _t, Member, uiTourService) {
Application.Controllers.controller('OpenAPIClientsController', ['$scope', 'clientsPromise', 'settingsPromise', 'growl', 'OpenAPIClient', 'dialogs', '_t', 'Member', 'uiTourService',
function ($scope, clientsPromise, settingsPromise, growl, OpenAPIClient, dialogs, _t, Member, uiTourService) {
/* PUBLIC SCOPE */
// clients list
@ -149,7 +149,7 @@ Application.Controllers.controller('OpenAPIClientsController', ['$scope', 'clien
}
});
// if the user has never seen the tour, and if the display behavior is not configured to manual triggering only, show the tour now
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('open-api') < 0) {
if (settingsPromise.feature_tour_display !== 'manual' && $scope.currentUser.profile.tours.indexOf('open-api') < 0) {
uitour.start();
}
};

View File

@ -18,8 +18,8 @@
/**
* Controller used in the prices edition page
*/
Application.Controllers.controller('EditPricingController', ['$scope', '$state', '$uibModal', '$filter', 'TrainingsPricing', 'Credit', 'Pricing', 'Plan', 'Coupon', 'plans', 'groups', 'growl', 'machinesPricesPromise', 'Price', 'dialogs', 'trainingsPricingsPromise', 'trainingsPromise', 'machineCreditsPromise', 'machinesPromise', 'trainingCreditsPromise', 'couponsPromise', 'spacesPromise', 'spacesPricesPromise', 'spacesCreditsPromise', '_t', 'Member', 'uiTourService',
function ($scope, $state, $uibModal, $filter, TrainingsPricing, Credit, Pricing, Plan, Coupon, plans, groups, growl, machinesPricesPromise, Price, dialogs, trainingsPricingsPromise, trainingsPromise, machineCreditsPromise, machinesPromise, trainingCreditsPromise, couponsPromise, spacesPromise, spacesPricesPromise, spacesCreditsPromise, _t, Member, uiTourService) {
Application.Controllers.controller('EditPricingController', ['$scope', '$state', '$uibModal', '$filter', 'TrainingsPricing', 'Credit', 'Pricing', 'Plan', 'Coupon', 'plans', 'groups', 'growl', 'machinesPricesPromise', 'Price', 'dialogs', 'trainingsPricingsPromise', 'trainingsPromise', 'machineCreditsPromise', 'machinesPromise', 'trainingCreditsPromise', 'couponsPromise', 'spacesPromise', 'spacesPricesPromise', 'spacesCreditsPromise', 'settingsPromise', '_t', 'Member', 'uiTourService',
function ($scope, $state, $uibModal, $filter, TrainingsPricing, Credit, Pricing, Plan, Coupon, plans, groups, growl, machinesPricesPromise, Price, dialogs, trainingsPricingsPromise, trainingsPromise, machineCreditsPromise, machinesPromise, trainingCreditsPromise, couponsPromise, spacesPromise, spacesPricesPromise, spacesCreditsPromise, settingsPromise, _t, Member, uiTourService) {
/* PUBLIC SCOPE */
// List of machines prices (not considering any plan)
@ -76,6 +76,9 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
// Default: we show only enabled plans
$scope.planFiltering = 'enabled';
// Default duration for the slots
$scope.slotDuration = parseInt(settingsPromise.slot_duration, 10);
// Available options for filtering plans by status
$scope.filterDisabled = [
'enabled',
@ -620,7 +623,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
/**
* Return the exemple price based on the configuration of the default slot duration.
* @param type {string} 'hourly_rate' | *
* @returns {number} price for Fablab.slotDuration minutes.
* @returns {number} price for "SLOT_DURATION" minutes.
*/
$scope.examplePrice = function(type) {
const hourlyRate = 10;
@ -629,7 +632,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
return $filter('currency')(hourlyRate);
}
const price = (hourlyRate / 60) * Fablab.slotDuration;
const price = (hourlyRate / 60) * $scope.slotDuration;
return $filter('currency')(price);
}
@ -673,7 +676,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
content: _t('app.admin.tour.pricing.machines.content'),
placement: 'bottom'
});
if (!Fablab.withoutSpaces) {
if ($scope.modules.spaces) {
uitour.createStep({
selector: '.plans-pricing .spaces-tab',
stepId: 'spaces',
@ -727,7 +730,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
}
});
// if the user has never seen the tour, show him now
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('pricing') < 0) {
if (settingsPromise.feature_tour_display !== 'manual' && $scope.currentUser.profile.tours.indexOf('pricing') < 0) {
uitour.start();
}
}

View File

@ -12,8 +12,8 @@
*/
'use strict';
Application.Controllers.controller('ProjectElementsController', ['$scope', '$state', 'Component', 'Licence', 'Theme', 'componentsPromise', 'licencesPromise', 'themesPromise', '_t', 'Member', 'uiTourService',
function ($scope, $state, Component, Licence, Theme, componentsPromise, licencesPromise, themesPromise, _t, Member, uiTourService) {
Application.Controllers.controller('AdminProjectsController', ['$scope', '$state', 'Component', 'Licence', 'Theme', 'componentsPromise', 'licencesPromise', 'themesPromise', '_t', 'Member', 'uiTourService', 'settingsPromise', 'growl',
function ($scope, $state, Component, Licence, Theme, componentsPromise, licencesPromise, themesPromise, _t, Member, uiTourService, settingsPromise, growl) {
// Materials list (plastic, wood ...)
$scope.components = componentsPromise;
@ -23,6 +23,12 @@ Application.Controllers.controller('ProjectElementsController', ['$scope', '$sta
// Themes list (cooking, sport ...)
$scope.themes = themesPromise;
// Application settings
$scope.allSettings = settingsPromise;
// default tab: materials
$scope.tabs = { active: 0 };
/**
* Saves a new component / Update an existing material to the server (form validation callback)
* @param data {Object} component name
@ -156,18 +162,61 @@ Application.Controllers.controller('ProjectElementsController', ['$scope', '$sta
};
/**
* Setup the feature-tour for the admin/project_elements page.
* When a file is sent to the server to test it against its MIME type,
* handle the result of the test.
*/
$scope.onTestFileComplete = function (res) {
if (res) {
growl.success(_t('app.admin.projects.settings.file_is_TYPE', { TYPE: res.type }));
}
};
/**
* For use with 'ng-class', returns the CSS class name for the uploads previews.
* The preview may show a placeholder or the content of the file depending on the upload state.
* @param v {*} any attribute, will be tested for truthiness (see JS evaluation rules)
*/
$scope.fileinputClass = function (v) {
if (v) {
return 'fileinput-exists';
} else {
return 'fileinput-new';
}
};
/**
* Remove the initial dot from the given extension, if any
* @param extension {String}
* @returns {String}
*/
$scope.removeInitialDot = function (extension) {
if (extension.substr(0, 1) === '.') return $scope.lower(extension.substr(1));
return $scope.lower(extension);
};
/**
* Return the lowercase version of the provided string
* @param text {String}
* @returns {string}
*/
$scope.lower = function (text) {
return text.toLowerCase();
};
/**
* Setup the feature-tour for the admin/projects page.
* This is intended as a contextual help (when pressing F1)
*/
$scope.setupProjectElementsTour = function () {
// get the tour defined by the ui-tour directive
const uitour = uiTourService.getTourByName('project-elements');
const uitour = uiTourService.getTourByName('projects');
uitour.createStep({
selector: 'body',
stepId: 'welcome',
order: 0,
title: _t('app.admin.tour.project_elements.welcome.title'),
content: _t('app.admin.tour.project_elements.welcome.content'),
title: _t('app.admin.tour.projects.welcome.title'),
content: _t('app.admin.tour.projects.welcome.content'),
placement: 'bottom',
orphan: true
});
@ -175,30 +224,43 @@ Application.Controllers.controller('ProjectElementsController', ['$scope', '$sta
selector: '.heading .abuses-button',
stepId: 'abuses',
order: 1,
title: _t('app.admin.tour.project_elements.abuses.title'),
content: _t('app.admin.tour.project_elements.abuses.content'),
title: _t('app.admin.tour.projects.abuses.title'),
content: _t('app.admin.tour.projects.abuses.content'),
placement: 'bottom',
popupClass: 'shift-left-40'
});
uitour.createStep({
selector: '.projects .settings-tab',
stepId: 'settings',
order: 2,
title: _t('app.admin.tour.projects.settings.title'),
content: _t('app.admin.tour.projects.settings.content'),
placement: 'bottom',
popupClass: 'shift-left-50'
});
uitour.createStep({
selector: 'body',
stepId: 'conclusion',
order: 2,
order: 3,
title: _t('app.admin.tour.conclusion.title'),
content: _t('app.admin.tour.conclusion.content'),
placement: 'bottom',
orphan: true
});
// on step change, change the active tab if needed
uitour.on('stepChanged', function (nextStep) {
if (nextStep.stepId === 'settings') { $scope.tabs.active = 3; }
});
// on tour end, save the status in database
uitour.on('ended', function () {
if (uitour.getStatus() === uitour.Status.ON && $scope.currentUser.profile.tours.indexOf('project-elements') < 0) {
Member.completeTour({ id: $scope.currentUser.id }, { tour: 'project-elements' }, function (res) {
if (uitour.getStatus() === uitour.Status.ON && $scope.currentUser.profile.tours.indexOf('projects') < 0) {
Member.completeTour({ id: $scope.currentUser.id }, { tour: 'projects' }, function (res) {
$scope.currentUser.profile.tours = res.tours;
});
}
});
// if the user has never seen the tour, show him now
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('project-elements') < 0) {
if (settingsPromise.feature_tour_display !== 'manual' && $scope.currentUser.profile.tours.indexOf('projects') < 0) {
uitour.start();
}
};

View File

@ -54,9 +54,10 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
// full history of privacy policy drafts
$scope.privacyDraftsHistory = [];
// all settings as retrieved from database
$scope.allSettings = settingsPromise;
// various configurable settings
$scope.twitterSetting = { name: 'twitter_name', value: settingsPromise.twitter_name };
$scope.linkName = { name: 'link_name', value: settingsPromise.link_name };
$scope.aboutTitleSetting = { name: 'about_title', value: settingsPromise.about_title };
$scope.aboutBodySetting = { name: 'about_body', value: settingsPromise.about_body };
$scope.privacyDpoSetting = { name: 'privacy_dpo', value: settingsPromise.privacy_dpo };
@ -74,9 +75,7 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
$scope.windowEnd = { name: 'booking_window_end', value: settingsPromise.booking_window_end };
$scope.mainColorSetting = { name: 'main_color', value: settingsPromise.main_color };
$scope.secondColorSetting = { name: 'secondary_color', value: settingsPromise.secondary_color };
$scope.fablabName = { name: 'fablab_name', value: settingsPromise.fablab_name };
$scope.nameGenre = { name: 'name_genre', value: settingsPromise.name_genre };
$scope.machinesSortBy = { name: 'machines_sort_by', value: settingsPromise.machines_sort_by };
$scope.cguFile = cguFile.custom_asset;
$scope.cgvFile = cgvFile.custom_asset;
$scope.customLogo = logoFile.custom_asset;
@ -84,56 +83,6 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
$scope.customFavicon = faviconFile.custom_asset;
$scope.profileImage = profileImageFile.custom_asset;
$scope.enableMove = {
name: 'booking_move_enable',
value: (settingsPromise.booking_move_enable === 'true')
};
$scope.moveDelay = {
name: 'booking_move_delay',
value: parseInt(settingsPromise.booking_move_delay, 10)
};
$scope.enableCancel = {
name: 'booking_cancel_enable',
value: (settingsPromise.booking_cancel_enable === 'true')
};
$scope.cancelDelay = {
name: 'booking_cancel_delay',
value: parseInt(settingsPromise.booking_cancel_delay, 10)
};
$scope.enableReminder = {
name: 'reminder_enable',
value: (settingsPromise.reminder_enable === 'true')
};
$scope.reminderDelay = {
name: 'reminder_delay',
value: parseInt(settingsPromise.reminder_delay, 10)
};
$scope.visibilityYearly = {
name: 'visibility_yearly',
value: parseInt(settingsPromise.visibility_yearly, 10)
};
$scope.visibilityOthers = {
name: 'visibility_others',
value: parseInt(settingsPromise.visibility_others, 10)
};
$scope.displayNameEnable = {
name: 'display_name_enable',
value: (settingsPromise.display_name_enable === 'true')
};
$scope.fabAnalytics = {
name: 'fab_analytics',
value: (settingsPromise.fab_analytics === 'true')
};
// By default, we display the currently published privacy policy
$scope.privacyPolicy = {
version: null,
@ -381,10 +330,18 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
placement: 'bottom',
orphan: true
});
uitour.createStep({
selector: '.admin-settings .general-page-tab',
stepId: 'general',
order: 1,
title: _t('app.admin.tour.settings.general.title'),
content: _t('app.admin.tour.settings.general.content'),
placement: 'bottom',
});
uitour.createStep({
selector: '.admin-settings .home-page-content h4',
stepId: 'home',
order: 1,
order: 2,
title: _t('app.admin.tour.settings.home.title'),
content: _t('app.admin.tour.settings.home.content'),
placement: 'bottom'
@ -392,7 +349,7 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
uitour.createStep({
selector: '.admin-settings .home-page-content .note-toolbar .note-insert div',
stepId: 'components',
order: 2,
order: 3,
title: _t('app.admin.tour.settings.components.title'),
content: _t('app.admin.tour.settings.components.content'),
placement: 'bottom'
@ -400,7 +357,7 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
uitour.createStep({
selector: '.admin-settings .home-page-content .note-toolbar .btn-codeview',
stepId: 'codeview',
order: 3,
order: 4,
title: _t('app.admin.tour.settings.codeview.title'),
content: _t('app.admin.tour.settings.codeview.content'),
placement: 'bottom'
@ -408,7 +365,7 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
uitour.createStep({
selector: '.admin-settings .reset-button',
stepId: 'reset',
order: 4,
order: 5,
title: _t('app.admin.tour.settings.reset.title'),
content: _t('app.admin.tour.settings.reset.content'),
placement: 'left'
@ -416,7 +373,7 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
uitour.createStep({
selector: '.admin-settings .home-page-style',
stepId: 'css',
order: 5,
order: 6,
title: _t('app.admin.tour.settings.css.title'),
content: _t('app.admin.tour.settings.css.content'),
placement: 'top'
@ -424,7 +381,7 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
uitour.createStep({
selector: '.admin-settings .about-page-tab',
stepId: 'about',
order: 6,
order: 7,
title: _t('app.admin.tour.settings.about.title'),
content: _t('app.admin.tour.settings.about.content'),
placement: 'bottom'
@ -432,7 +389,7 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
uitour.createStep({
selector: '.admin-settings .privacy-page-tab',
stepId: 'privacy',
order: 7,
order: 8,
title: _t('app.admin.tour.settings.privacy.title'),
content: _t('app.admin.tour.settings.privacy.content'),
placement: 'bottom'
@ -440,15 +397,24 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
uitour.createStep({
selector: '.admin-settings .history-select',
stepId: 'draft',
order: 8,
order: 9,
title: _t('app.admin.tour.settings.draft.title'),
content: _t('app.admin.tour.settings.draft.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.admin-settings .reservations-page-tab',
stepId: 'reservations',
order: 10,
title: _t('app.admin.tour.settings.reservations.title'),
content: _t('app.admin.tour.settings.reservations.content'),
placement: 'bottom',
popupClass: 'shift-left-50'
});
uitour.createStep({
selector: 'body',
stepId: 'conclusion',
order: 9,
order: 11,
title: _t('app.admin.tour.conclusion.title'),
content: _t('app.admin.tour.conclusion.content'),
placement: 'bottom',
@ -456,9 +422,11 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
});
// on step change, change the active tab if needed
uitour.on('stepChanged', function (nextStep) {
if (nextStep.stepId === 'general') { $scope.tabs.active = 0; }
if (nextStep.stepId === 'home' || nextStep.stepId === 'css') { $scope.tabs.active = 1; }
if (nextStep.stepId === 'about') { $scope.tabs.active = 2; }
if (nextStep.stepId === 'privacy' || nextStep.stepId === 'draft') { $scope.tabs.active = 3; }
if (nextStep.stepId === 'reservations') { $scope.tabs.active = 4; }
});
// on tour end, save the status in database
uitour.on('ended', function () {
@ -469,7 +437,7 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
}
});
// if the user has never seen the tour, show him now
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('settings') < 0) {
if ($scope.allSettings.feature_tour_display !== 'manual' && $scope.currentUser.profile.tours.indexOf('settings') < 0) {
uitour.start();
}
}
@ -524,6 +492,19 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
$scope.$watch('advancedSettings.open', function (newValue) {
if (newValue) $scope.codeMirrorEditor.refresh();
})
// use the tours list, based on the selected value
$scope.$watch('allSettings.feature_tour_display', function (newValue, oldValue, scope) {
if (newValue === oldValue) return;
if (newValue === 'session') {
$scope.currentUser.profile.tours = Fablab.sessionTours;
} else if (newValue === 'once') {
Member.get({ id: $scope.currentUser.id }, function (user) {
$scope.currentUser.profile.tours = user.profile.tours;
});
}
});
};
// init the controller (call at the end !)

View File

@ -15,8 +15,8 @@
*/
'use strict';
Application.Controllers.controller('StatisticsController', ['$scope', '$state', '$rootScope', '$uibModal', 'es', 'Member', '_t', 'membersPromise', 'statisticsPromise', 'uiTourService',
function ($scope, $state, $rootScope, $uibModal, es, Member, _t, membersPromise, statisticsPromise, uiTourService) {
Application.Controllers.controller('StatisticsController', ['$scope', '$state', '$rootScope', '$uibModal', 'es', 'Member', '_t', 'membersPromise', 'statisticsPromise', 'uiTourService', 'settingsPromise',
function ($scope, $state, $rootScope, $uibModal, es, Member, _t, membersPromise, statisticsPromise, uiTourService, settingsPromise) {
/* PRIVATE STATIC CONSTANTS */
// search window size
@ -179,9 +179,9 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
*/
$scope.hiddenTab = function (tab) {
if (tab.table) {
if ((tab.es_type_key === 'subscription') && $rootScope.fablabWithoutPlans) {
if ((tab.es_type_key === 'subscription') && !$rootScope.modules.plans) {
return true;
} else return (tab.es_type_key === 'space') && $rootScope.fablabWithoutSpaces;
} else return (tab.es_type_key === 'space') && !$rootScope.modules.spaces;
} else {
return true;
}
@ -388,7 +388,7 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
}
});
// if the user has never seen the tour, show him now
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('statistics') < 0) {
if (settingsPromise.feature_tour_display !== 'manual' && $scope.currentUser.profile.tours.indexOf('statistics') < 0) {
uitour.start();
}
}

View File

@ -150,8 +150,8 @@ Application.Controllers.controller('EditTrainingController', [ '$scope', '$state
/**
* Controller used in the trainings management page, allowing admins users to see and manage the list of trainings and reservations.
*/
Application.Controllers.controller('TrainingsAdminController', ['$scope', '$state', '$uibModal', 'Training', 'trainingsPromise', 'machinesPromise', '_t', 'growl', 'dialogs', 'Member', 'uiTourService',
function ($scope, $state, $uibModal, Training, trainingsPromise, machinesPromise, _t, growl, dialogs, Member, uiTourService) {
Application.Controllers.controller('TrainingsAdminController', ['$scope', '$state', '$uibModal', 'Training', 'trainingsPromise', 'machinesPromise', '_t', 'growl', 'dialogs', 'Member', 'uiTourService', 'settingsPromise',
function ($scope, $state, $uibModal, Training, trainingsPromise, machinesPromise, _t, growl, dialogs, Member, uiTourService, settingsPromise) {
// list of trainings
$scope.trainings = trainingsPromise;
@ -398,7 +398,7 @@ Application.Controllers.controller('TrainingsAdminController', ['$scope', '$stat
}
});
// if the user has never seen the tour, show him now
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('trainings') < 0) {
if (settingsPromise.feature_tour_display !== 'manual' && $scope.currentUser.profile.tours.indexOf('trainings') < 0) {
uitour.start();
}
}

View File

@ -91,7 +91,10 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
return $uibModal.open({
templateUrl: '<%= asset_path "shared/signupModal.html" %>',
size: 'md',
controller: ['$scope', '$uibModalInstance', 'Group', 'CustomAsset', 'growl', '_t', function ($scope, $uibModalInstance, Group, CustomAsset, growl, _t) {
resolve: {
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['phone_required', 'recaptcha_site_key', 'confirmation_required']" }).$promise; }]
},
controller: ['$scope', '$uibModalInstance', 'Group', 'CustomAsset', 'settingsPromise', 'growl', '_t', function ($scope, $uibModalInstance, Group, CustomAsset, settingsPromise, growl, _t) {
// default parameters for the date picker in the account creation modal
$scope.datePicker = {
format: Fablab.uibDateFormat,
@ -101,8 +104,11 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
}
};
// is the phone number required to sign-up?
$scope.phoneRequired = (settingsPromise.phone_required === 'true');
// reCaptcha v2 site key (or undefined)
$scope.recaptchaSiteKey = Fablab.recaptchaSiteKey;
$scope.recaptchaSiteKey = settingsPromise.recaptcha_site_key;
// callback to open the date picker (account creation modal)
$scope.openDatePicker = function ($event) {
@ -147,7 +153,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
return Auth.register($scope.user).then(function (user) {
if (user.id) {
// creation successful
$uibModalInstance.close(user);
$uibModalInstance.close({ user, settings: settingsPromise });
} else {
// the user was not saved in database, something wrong occurred
growl.error(_t('app.public.common.unexpected_error_occurred'));
@ -168,13 +174,13 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
});
};
}]
}).result['finally'](null).then(function (user) {
}).result['finally'](null).then(function (res) {
// when the account was created successfully, set the session to the newly created account
if(Fablab.userConfirmationNeededToSignIn) {
if(res.settings.confirmation_required) {
Auth._currentUser = null;
growl.info(_t('app.public.common.you_will_receive_confirmation_instructions_by_email_detailed'));
} else {
$scope.setCurrentUser(user);
$scope.setCurrentUser(res.user);
}
});
<% end %>
@ -403,8 +409,15 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
return $uibModal.open({
templateUrl: '<%= asset_path "shared/deviseModal.html" %>',
size: 'sm',
controller: ['$scope', '$uibModalInstance', '_t', function ($scope, $uibModalInstance, _t) {
resolve: {
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['confirmation_required']" }).$promise; }]
},
controller: ['$scope', '$uibModalInstance', '_t', 'settingsPromise', function ($scope, $uibModalInstance, _t, settingsPromise) {
const user = ($scope.user = {});
// email confirmation required before user sign-in?
$scope.confirmationRequired = settingsPromise.confirmation_required;
$scope.login = function () {
Auth.login(user).then(function (user) {
// Authentication succeeded ...
@ -435,7 +448,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
return $uibModalInstance.dismiss('confirmationNew');
};
return $scope.openResetPassword = function (e) {
$scope.openResetPassword = function (e) {
e.preventDefault();
return $uibModalInstance.dismiss('resetPassword');
};

View File

@ -32,7 +32,7 @@ Application.Controllers.controller('CookiesController', ['$scope', '$cookies', '
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', Fablab.gaId, 'auto');
ga('create', Fablab.trackingId, 'auto');
ga('send', 'pageview');
/* eslint-enable */
};
@ -50,8 +50,8 @@ Application.Controllers.controller('CookiesController', ['$scope', '$cookies', '
$scope.learnMoreUrl = '#!/privacy-policy';
}
});
// if the GA_ID environment variable was not set, only functional cookies will be set, so user consent is not required
if (!Fablab.gaId) $scope.cookiesState = 'ignore';
// if the tracking ID was not set in the settings, only functional cookies will be set, so user consent is not required
if (!Fablab.trackingId) $scope.cookiesState = 'ignore';
};
const readCookie = function () {

View File

@ -13,8 +13,8 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
Application.Controllers.controller('EventsController', ['$scope', '$state', 'Event', 'categoriesPromise', 'themesPromise', 'ageRangesPromise',
function ($scope, $state, Event, categoriesPromise, themesPromise, ageRangesPromise) {
Application.Controllers.controller('EventsController', ['$scope', '$state', 'Event', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'settingsPromise',
function ($scope, $state, Event, categoriesPromise, themesPromise, ageRangesPromise, settingsPromise) {
/* PUBLIC SCOPE */
// The events displayed on the page
@ -305,7 +305,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
const amountToPay = helpers.getAmountToPay($scope.reserve.amountTotal, wallet.amount);
if ((AuthService.isAuthorized(['member']) && amountToPay > 0)
|| (AuthService.isAuthorized('manager') && $scope.ctrl.member.id === $rootScope.currentUser.id && amountToPay > 0)) {
if ($rootScope.fablabWithoutOnlinePayment) {
if (settingsPromise.online_payment_module !== 'true') {
growl.error(_t('app.public.events_show.online_payment_disabled'));
} else {
return payByStripe(reservation);
@ -698,10 +698,11 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
},
cartItems () {
return mkRequestParams(reservation, $scope.coupon.applied);
}
},
stripeKey: ['Setting', function (Setting) { return Setting.get({ name: 'stripe_public_key' }).$promise; }]
},
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'growl', 'wallet', 'helpers', '$filter', 'coupon', 'cartItems',
function ($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, growl, wallet, helpers, $filter, coupon, cartItems) {
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'growl', 'wallet', 'helpers', '$filter', 'coupon', 'cartItems', 'stripeKey',
function ($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, growl, wallet, helpers, $filter, coupon, cartItems, stripeKey) {
// User's wallet amount
$scope.walletAmount = wallet.amount;
@ -720,8 +721,11 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
// Used in wallet info template to interpolate some translations
$scope.numberFilter = $filter('number');
// stripe publishable key
$scope.stripeKey = stripeKey.setting.value;
// Callback to handle the post-payment and reservation
return $scope.onPaymentSuccess = function (reservation) {
$scope.onPaymentSuccess = function (reservation) {
$uibModalInstance.close(reservation);
};
}
@ -859,6 +863,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
controller: 'ReserveSlotSameTimeController',
resolve: {
sameTimeReservations: function() { return sameTimeReservations; },
bookOverlappingSlotsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'book_overlapping_slots' }).$promise; }]
}
});
modalInstance.result.then(callback);

View File

@ -1,7 +1,7 @@
'use strict';
Application.Controllers.controller('HomeController', ['$scope', '$stateParams', '$translatePartialLoader', 'AuthService', 'settingsPromise', 'Member', 'uiTourService', '_t', 'Help',
function ($scope, $stateParams, $translatePartialLoader, AuthService, settingsPromise, Member, uiTourService, _t, Help) {
Application.Controllers.controller('HomeController', ['$scope', '$stateParams', '$translatePartialLoader', 'AuthService', 'settingsPromise', 'Member', 'uiTourService', '_t',
function ($scope, $stateParams, $translatePartialLoader, AuthService, settingsPromise, Member, uiTourService, _t) {
/* PUBLIC SCOPE */
// Home page HTML content
@ -140,7 +140,7 @@ Application.Controllers.controller('HomeController', ['$scope', '$stateParams',
content: _t('app.public.tour.welcome.machines.content'),
placement: 'right'
});
if (!Fablab.withoutSpaces) {
if ($scope.modules.spaces) {
uitour.createStep({
selector: '.nav-primary li.reserve-space-link',
stepId: 'spaces',
@ -303,7 +303,7 @@ Application.Controllers.controller('HomeController', ['$scope', '$stateParams',
}
});
// if the user has never seen the tour, show him now
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('welcome') < 0) {
if (settingsPromise.feature_tour_display !== 'manual' && $scope.currentUser.profile.tours.indexOf('welcome') < 0) {
uitour.start();
}
};

View File

@ -180,8 +180,8 @@ const _reserveMachine = function (machine, e) {
/**
* Controller used in the public listing page, allowing everyone to see the list of machines
*/
Application.Controllers.controller('MachinesController', ['$scope', '$state', '_t', 'AuthService', 'Machine', '$uibModal', 'machinesPromise', 'Member', 'uiTourService',
function ($scope, $state, _t, AuthService, Machine, $uibModal, machinesPromise, Member, uiTourService) {
Application.Controllers.controller('MachinesController', ['$scope', '$state', '_t', 'AuthService', 'Machine', '$uibModal', 'machinesPromise', 'settingsPromise', 'Member', 'uiTourService',
function ($scope, $state, _t, AuthService, Machine, $uibModal, machinesPromise, settingsPromise, Member, uiTourService) {
/* PUBLIC SCOPE */
// Retrieve the list of machines
@ -281,7 +281,7 @@ Application.Controllers.controller('MachinesController', ['$scope', '$state', '_
}
});
// if the user has never seen the tour, show him now
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('machines') < 0) {
if (settingsPromise.feature_tour_display !== 'manual' && $scope.currentUser.profile.tours.indexOf('machines') < 0) {
uitour.start();
}
}

View File

@ -58,7 +58,7 @@ Application.Controllers.controller('MainNavController', ['$scope', function ($sc
];
if (!Fablab.withoutPlans) {
if ($scope.modules.plans) {
$scope.navLinks.push({
state: 'app.public.plans',
linkText: 'app.public.common.subscriptions',
@ -67,7 +67,7 @@ Application.Controllers.controller('MainNavController', ['$scope', function ($sc
});
}
if (!Fablab.withoutSpaces) {
if ($scope.modules.spaces) {
$scope.navLinks.splice(4, 0, {
state: 'app.public.spaces_list',
linkText: 'app.public.common.reserve_a_space',
@ -138,8 +138,8 @@ Application.Controllers.controller('MainNavController', ['$scope', function ($sc
authorizedRoles: ['admin']
},
{
state: 'app.admin.project_elements',
linkText: 'app.public.common.manage_the_projects_elements',
state: 'app.admin.projects',
linkText: 'app.public.common.projects',
linkIcon: 'tasks',
authorizedRoles: ['admin']
},
@ -153,7 +153,7 @@ Application.Controllers.controller('MainNavController', ['$scope', function ($sc
$scope.adminNavLinks = adminNavLinks;
if (!Fablab.withoutSpaces) {
if ($scope.modules.spaces) {
return $scope.adminNavLinks.splice(3, 0, {
state: 'app.public.spaces_list',
linkText: 'app.public.common.manage_the_spaces',

View File

@ -72,8 +72,8 @@ Application.Controllers.controller('MembersController', ['$scope', 'Member', 'me
/**
* Controller used when editing the current user's profile (in dashboard)
*/
Application.Controllers.controller('EditProfileController', ['$scope', '$rootScope', '$state', '$window', '$sce', '$cookies', '$injector', 'Member', 'Auth', 'Session', 'activeProviderPromise', 'growl', 'dialogs', 'CSRF', 'memberPromise', 'groups', '_t',
function ($scope, $rootScope, $state, $window, $sce, $cookies, $injector, Member, Auth, Session, activeProviderPromise, growl, dialogs, CSRF, memberPromise, groups, _t) {
Application.Controllers.controller('EditProfileController', ['$scope', '$rootScope', '$state', '$window', '$sce', '$cookies', '$injector', 'Member', 'Auth', 'Session', 'activeProviderPromise', 'phoneRequiredPromise', 'growl', 'dialogs', 'CSRF', 'memberPromise', 'groups', '_t',
function ($scope, $rootScope, $state, $window, $sce, $cookies, $injector, Member, Auth, Session, activeProviderPromise, phoneRequiredPromise, growl, dialogs, CSRF, memberPromise, groups, _t) {
/* PUBLIC SCOPE */
// API URL where the form will be posted
@ -110,6 +110,9 @@ Application.Controllers.controller('EditProfileController', ['$scope', '$rootSco
// Should the passord be modified?
$scope.password = { change: false };
// is the phone number required in _member_form?
$scope.phoneRequired = (phoneRequiredPromise.setting.value === 'true');
// Angular-Bootstrap datepicker configuration for birthday
$scope.datePicker = {
format: Fablab.uibDateFormat,

View File

@ -12,8 +12,8 @@
*/
'use strict';
Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScope', '$state', '$uibModal', 'Auth', 'AuthService', 'dialogs', 'growl', 'plansPromise', 'groupsPromise', 'Subscription', 'Member', 'subscriptionExplicationsPromise', '_t', 'Wallet', 'helpers',
function ($scope, $rootScope, $state, $uibModal, Auth, AuthService, dialogs, growl, plansPromise, groupsPromise, Subscription, Member, subscriptionExplicationsPromise, _t, Wallet, helpers) {
Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScope', '$state', '$uibModal', 'Auth', 'AuthService', 'dialogs', 'growl', 'plansPromise', 'groupsPromise', 'Subscription', 'Member', 'subscriptionExplicationsPromise', '_t', 'Wallet', 'helpers', 'settingsPromise',
function ($scope, $rootScope, $state, $uibModal, Auth, AuthService, dialogs, growl, plansPromise, groupsPromise, Subscription, Member, subscriptionExplicationsPromise, _t, Wallet, helpers, settingsPromise) {
/* PUBLIC SCOPE */
// list of groups
@ -92,7 +92,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
const amountToPay = helpers.getAmountToPay($scope.cart.total, wallet.amount);
if ((AuthService.isAuthorized('member') && amountToPay > 0)
|| (AuthService.isAuthorized('manager') && $scope.ctrl.member.id === $rootScope.currentUser.id && amountToPay > 0)) {
if ($rootScope.fablabWithoutOnlinePayment) {
if (settingsPromise.online_payment_module !== 'true') {
growl.error(_t('app.public.plans.online_payment_disabled'));
} else {
return payByStripe();
@ -244,10 +244,11 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
wallet () {
return Wallet.getWalletByUser({ user_id: $scope.ctrl.member.id }).$promise;
},
coupon () { return $scope.coupon.applied; }
coupon () { return $scope.coupon.applied; },
stripeKey: ['Setting', function (Setting) { return Setting.get({ name: 'stripe_public_key' }).$promise; }]
},
controller: ['$scope', '$uibModalInstance', '$state', 'selectedPlan', 'member', 'price', 'Subscription', 'CustomAsset', 'wallet', 'helpers', '$filter', 'coupon',
function ($scope, $uibModalInstance, $state, selectedPlan, member, price, Subscription, CustomAsset, wallet, helpers, $filter, coupon) {
controller: ['$scope', '$uibModalInstance', '$state', 'selectedPlan', 'member', 'price', 'Subscription', 'CustomAsset', 'wallet', 'helpers', '$filter', 'coupon', 'stripeKey',
function ($scope, $uibModalInstance, $state, selectedPlan, member, price, Subscription, CustomAsset, wallet, helpers, $filter, coupon, stripeKey) {
// User's wallet amount
$scope.walletAmount = wallet.amount;
@ -268,6 +269,9 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
}
};
// stripe publishable key
$scope.stripeKey = stripeKey.setting.value;
// retrieve the CGV
CustomAsset.get({ name: 'cgv-file' }, function (cgv) { $scope.cgv = cgv.custom_asset; });

View File

@ -13,8 +13,8 @@
'use strict';
Application.Controllers.controller('CompleteProfileController', ['$scope', '$rootScope', '$state', '$window', '_t', 'growl', 'CSRF', 'Auth', 'Member', 'settingsPromise', 'activeProviderPromise', 'groupsPromise', 'cguFile', 'memberPromise', 'Session', 'dialogs', 'AuthProvider',
function ($scope, $rootScope, $state, $window, _t, growl, CSRF, Auth, Member, settingsPromise, activeProviderPromise, groupsPromise, cguFile, memberPromise, Session, dialogs, AuthProvider) {
Application.Controllers.controller('CompleteProfileController', ['$scope', '$rootScope', '$state', '$window', '_t', 'growl', 'CSRF', 'Auth', 'Member', 'settingsPromise', 'activeProviderPromise', 'groupsPromise', 'cguFile', 'memberPromise', 'Session', 'dialogs', 'AuthProvider', 'phoneRequiredPromise',
function ($scope, $rootScope, $state, $window, _t, growl, CSRF, Auth, Member, settingsPromise, activeProviderPromise, groupsPromise, cguFile, memberPromise, Session, dialogs, AuthProvider, phoneRequiredPromise) {
/* PUBLIC SCOPE */
// API URL where the form will be posted
@ -47,6 +47,9 @@ Application.Controllers.controller('CompleteProfileController', ['$scope', '$roo
// CGU
$scope.cgu = cguFile.custom_asset;
// is the phone number required in _member_form?
$scope.phoneRequired = (phoneRequiredPromise.setting.value === 'true');
// Angular-Bootstrap datepicker configuration for birthday
$scope.datePicker = {
format: Fablab.uibDateFormat,

View File

@ -87,7 +87,7 @@ class ProjectsController {
$scope.totalSteps = $scope.project.project_steps_attributes.length;
// List of extensions allowed for CAD attachements upload
$scope.allowedExtensions = allowedExtensions;
$scope.allowedExtensions = allowedExtensions.setting.value.split(' ');
/**
* For use with ngUpload (https://github.com/twilson63/ngUpload).
@ -266,8 +266,8 @@ class ProjectsController {
/**
* Controller used on projects listing page
*/
Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'Project', 'machinesPromise', 'themesPromise', 'componentsPromise', 'paginationService', 'OpenlabProject', '$window', 'growl', '_t', '$location', '$timeout',
function ($scope, $state, Project, machinesPromise, themesPromise, componentsPromise, paginationService, OpenlabProject, $window, growl, _t, $location, $timeout) {
Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'Project', 'machinesPromise', 'themesPromise', 'componentsPromise', 'paginationService', 'OpenlabProject', '$window', 'growl', '_t', '$location', '$timeout', 'settingsPromise', 'openLabActive',
function ($scope, $state, Project, machinesPromise, themesPromise, componentsPromise, paginationService, OpenlabProject, $window, growl, _t, $location, $timeout, settingsPromise, openLabActive) {
/* PRIVATE STATIC CONSTANTS */
// Number of projects added to the page when the user clicks on 'load more projects'
@ -277,11 +277,11 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
/* PUBLIC SCOPE */
// Fab-manager's instance ID in the openLab network
$scope.openlabAppId = Fablab.openlabAppId;
$scope.openlabAppId = settingsPromise.openlab_app_id
// Is openLab enabled on the instance?
$scope.openlab = {
projectsActive: Fablab.openlabProjectsActive,
projectsActive: openLabActive.isPresent,
searchOverWholeNetwork: false
};
@ -390,32 +390,32 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
if ($location.$$search.whole_network === 'f') {
$scope.openlab.searchOverWholeNetwork = false;
} else {
$scope.openlab.searchOverWholeNetwork = ($scope.openlab.projectsActive && Fablab.openlabDefault) || false;
$scope.openlab.searchOverWholeNetwork = ($scope.openlab.projectsActive && settingsPromise.openlab_default === 'true') || false;
}
return $scope.triggerSearch();
};
/**
* function to update url query param, little hack to turn off reloadOnSearch and re-enable it after setting the params
* function to update url query param, little hack to turn off reloadOnSearch and re-enable it after we set the params.
* params example: 'q' , 'presse-purée'
*/
var updateUrlParam = function (name, value) {
const updateUrlParam = function (name, value) {
$state.current.reloadOnSearch = false;
$location.search(name, value);
return $timeout(function () { $state.current.reloadOnSearch = undefined; });
};
var loadMoreCallback = function (projectsPromise) {
const loadMoreCallback = function (projectsPromise) {
$scope.projects = $scope.projects.concat(projectsPromise.projects);
return updateUrlParam('page', $scope.projectsPagination.currentPage);
};
var loadMoreOpenlabCallback = function (projectsPromise) {
const loadMoreOpenlabCallback = function (projectsPromise) {
$scope.projects = $scope.projects.concat(normalizeProjectsAttrs(projectsPromise.projects));
return updateUrlParam('page', $scope.projectsPagination.currentPage);
};
var normalizeProjectsAttrs = function (projects) {
const normalizeProjectsAttrs = function (projects) {
return projects.map(function (project) {
project.project_image = project.image_url;
return project;
@ -501,14 +501,14 @@ Application.Controllers.controller('EditProjectController', ['$rootScope', '$sco
/**
* Controller used in the public project's details page
*/
Application.Controllers.controller('ShowProjectController', ['$scope', '$state', 'projectPromise', '$location', '$uibModal', 'dialogs', '_t',
function ($scope, $state, projectPromise, $location, $uibModal, dialogs, _t) {
Application.Controllers.controller('ShowProjectController', ['$scope', '$state', 'projectPromise', 'shortnamePromise', '$location', '$uibModal', 'dialogs', '_t',
function ($scope, $state, projectPromise, shortnamePromise, $location, $uibModal, dialogs, _t) {
/* PUBLIC SCOPE */
// Store the project's details
$scope.project = projectPromise;
$scope.projectUrl = $location.absUrl();
$scope.disqusShortname = Fablab.disqusShortname;
$scope.disqusShortname = shortnamePromise.setting.value;
/**
* Test if the provided user has the edition rights on the current project

View File

@ -98,8 +98,8 @@ class SpacesController {
/**
* Controller used in the public listing page, allowing everyone to see the list of spaces
*/
Application.Controllers.controller('SpacesController', ['$scope', '$state', 'spacesPromise', 'AuthService', '_t', 'Member', 'uiTourService',
function ($scope, $state, spacesPromise, AuthService, _t, Member, uiTourService) {
Application.Controllers.controller('SpacesController', ['$scope', '$state', 'spacesPromise', 'AuthService', '_t', 'Member', 'uiTourService', 'settingsPromise',
function ($scope, $state, spacesPromise, AuthService, _t, Member, uiTourService, settingsPromise) {
/* PUBLIC SCOPE */
// Retrieve the list of spaces
@ -193,7 +193,7 @@ Application.Controllers.controller('SpacesController', ['$scope', '$state', 'spa
}
});
// if the user has never seen the tour, show him now
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('spaces') < 0) {
if (settingsPromise.feature_tour_display !== 'manual' && $scope.currentUser.profile.tours.indexOf('spaces') < 0) {
uitour.start();
}
}

View File

@ -361,7 +361,8 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
size: 'md',
controller: 'ReserveSlotSameTimeController',
resolve: {
sameTimeReservations: function() { return sameTimeReservations; }
sameTimeReservations: function() { return sameTimeReservations; },
bookOverlappingSlotsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'book_overlapping_slots' }).$promise; }]
}
});
modalInstance.result.then(function(res) {
@ -631,10 +632,11 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
},
cartItems () {
return mkRequestParams(reservation, $scope.coupon.applied);
}
},
stripeKey: ['Setting', function (Setting) { return Setting.get({ name: 'stripe_public_key' }).$promise; }]
},
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon', 'cartItems',
function ($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, wallet, helpers, $filter, coupon, cartItems) {
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon', 'cartItems', 'stripeKey',
function ($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, wallet, helpers, $filter, coupon, cartItems, stripeKey) {
// user wallet amount
$scope.walletAmount = wallet.amount;
@ -653,6 +655,9 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
// Used in wallet info template to interpolate some translations
$scope.numberFilter = $filter('number');
// stripe publishable key
$scope.stripeKey = stripeKey.setting.value;
/**
* Callback to handle the post-payment and reservation
*/
@ -758,7 +763,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
const amountToPay = helpers.getAmountToPay($scope.amountTotal, wallet.amount);
if ((AuthService.isAuthorized(['member']) && amountToPay > 0)
|| (AuthService.isAuthorized('manager') && $scope.user.id === $rootScope.currentUser.id && amountToPay > 0)) {
if ($rootScope.fablabWithoutOnlinePayment) {
if ($scope.settings.online_payment_module !== 'true') {
growl.error(_t('app.shared.cart.online_payment_disabled'));
} else {
return payByStripe(reservation);
@ -783,10 +788,10 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
/**
* Controller of the modal showing the reservations the same date at the same time
*/
Application.Controllers.controller('ReserveSlotSameTimeController', ['$scope', '$uibModalInstance', 'AuthService', 'sameTimeReservations',
function ($scope, $uibModalInstance, AuthService, sameTimeReservations) {
Application.Controllers.controller('ReserveSlotSameTimeController', ['$scope', '$uibModalInstance', 'AuthService', 'sameTimeReservations', 'bookOverlappingSlotsPromise',
function ($scope, $uibModalInstance, AuthService, sameTimeReservations, bookOverlappingSlotsPromise) {
$scope.sameTimeReservations = sameTimeReservations;
$scope.bookSlotAtSameTime = Fablab.bookSlotAtSameTime;
$scope.bookSlotAtSameTime = (bookOverlappingSlotsPromise.setting.value === 'true');
$scope.isAuthorized = AuthService.isAuthorized;
/**
* Confirmation callback

View File

@ -0,0 +1,89 @@
Application.Directives.directive('booleanSetting', ['Setting', 'growl', '_t',
function (Setting, growl, _t) {
return ({
restrict: 'E',
scope: {
name: '@',
label: '@',
settings: '=',
yesLabel: '@',
noLabel: '@',
classes: '@',
onBeforeSave: '='
},
templateUrl: '<%= asset_path "admin/settings/boolean.html" %>',
link ($scope, element, attributes) {
// The setting
$scope.setting = {
name: $scope.name,
value: ($scope.settings[$scope.name] === 'true')
};
// default values for the switch labels
$scope.yesLabel = $scope.yesLabel || 'app.admin.settings.enabled';
$scope.noLabel = $scope.noLabel || 'app.admin.settings.disabled';
/**
* Callback to save the setting value to the database
* @param setting {{value:*, name:string}} note that the value will be stringified
*/
$scope.save = function (setting) {
if (typeof $scope.onBeforeSave === 'function') {
const res = $scope.onBeforeSave(setting);
if (res && _.isFunction(res.then)) {
// res is a promise, wait for it before proceed
res.then(function (success) {
if (success) saveValue(setting);
else resetValue();
}, function () {
resetValue();
});
} else {
if (res) saveValue(setting);
else resetValue();
}
} else {
saveValue(setting);
}
};
/* PRIVATE SCOPE */
/**
* Save the setting's new value in DB
* @param setting
*/
const saveValue = function (setting) {
const value = setting.value.toString();
Setting.update(
{ name: setting.name },
{ value },
function () {
growl.success(_t('app.admin.settings.customization_of_SETTING_successfully_saved', { SETTING: _t(`app.admin.settings.${setting.name}`) }));
$scope.settings[$scope.name] = value;
},
function (error) {
if (error.status === 304) return;
if (error.status === 423) {
growl.error(_t('app.admin.settings.error_SETTING_locked', { SETTING: _t(`app.admin.settings.${setting.name}`) }));
return;
}
growl.error(_t('app.admin.settings.an_error_occurred_saving_the_setting'));
console.log(error);
}
);
}
/**
* Reset the value of the setting to its original state (when the component loads)
*/
const resetValue = function () {
$scope.setting.value = $scope.settings[$scope.name] === 'true';
}
}
});
}
]);

View File

@ -0,0 +1,58 @@
Application.Directives.directive('numberSetting', ['Setting', 'growl', '_t',
function (Setting, growl, _t) {
return ({
restrict: 'E',
scope: {
name: '@',
label: '@',
settings: '=',
classes: '@',
faIcon: '@',
helperText: '@',
min: '@',
required: '<'
},
templateUrl: '<%= asset_path "admin/settings/number.html" %>',
link ($scope, element, attributes) {
// The setting
$scope.setting = {
name: $scope.name,
value: parseInt($scope.settings[$scope.name], 10)
};
/**
* Callback to save the setting value to the database
* @param setting {{value:*, name:string}} note that the value will be stringified
*/
$scope.save = function (setting) {
let value;
if (typeof setting.value === 'number') {
value = setting.value.toString();
} else {
({ value } = setting);
}
Setting.update(
{ name: setting.name },
{ value },
function () {
growl.success(_t('app.admin.settings.customization_of_SETTING_successfully_saved', { SETTING: _t(`app.admin.settings.${setting.name}`) }));
$scope.settings[$scope.name] = value;
},
function (error) {
if (error.status === 304) return;
if (error.status === 423) {
growl.error(_t('app.admin.settings.error_SETTING_locked', { SETTING: _t(`app.admin.settings.${setting.name}`) }));
return;
}
growl.error(_t('app.admin.settings.an_error_occurred_saving_the_setting'));
console.log(error);
}
);
};
}
});
}
]);

View File

@ -0,0 +1,103 @@
Application.Directives.directive('selectMultipleSetting', ['Setting', 'growl', '_t', '$uibModal',
function (Setting, growl, _t, $uibModal) {
return ({
restrict: 'E',
scope: {
name: '@',
label: '@',
settings: '=',
classes: '@',
required: '<',
titleNew: '@',
descriptionNew: '@',
beforeAdd: '='
},
templateUrl: '<%= asset_path "admin/settings/select-multiple.html" %>',
link ($scope, element, attributes) {
// The setting
$scope.setting = {
name: $scope.name,
value: $scope.settings[$scope.name]
};
// the options
$scope.options = $scope.settings[$scope.name].split(' ');
// the selected options
$scope.selection = [];
/**
* Remove the items in the selection from the options and update setting.value
*/
$scope.removeItem = function() {
const options = $scope.options.filter(function (opt) {
return $scope.selection.indexOf(opt) < 0;
})
$scope.options = options;
$scope.setting.value = options.join(' ');
growl.success(_t('app.admin.settings.COUNT_items_removed', { COUNT: $scope.selection.length }));
$scope.selection = [];
}
/**
* Open a modal dialog asking for the value of a new item to add
*/
$scope.addItem = function() {
$uibModal.open({
templateUrl: 'newSelectOption.html',
resolve: {
titleNew: function () { return $scope.titleNew; },
descriptionNew: function () { return $scope.descriptionNew; }
},
controller: function ($scope, $uibModalInstance, titleNew, descriptionNew) {
$scope.value = undefined;
$scope.titleNew = titleNew;
$scope.descriptionNew = descriptionNew;
$scope.ok = function () {
$uibModalInstance.close($scope.value);
};
$scope.dismiss = function () {
$uibModalInstance.dismiss('cancel');
};
}
}).result['finally'](null).then(function(val) {
const options = Array.from($scope.options);
if (typeof $scope.beforeAdd === 'function') { val = $scope.beforeAdd(val); }
options.push(val);
$scope.options = options;
$scope.setting.value = options.join(' ');
growl.success(_t('app.admin.settings.item_added'));
});
}
/**
* Callback to save the setting value to the database
* @param setting {{value:*, name:string}} note that the value will be stringified
*/
$scope.save = function (setting) {
let { value } = setting;
Setting.update(
{ name: setting.name },
{ value },
function () {
growl.success(_t('app.admin.settings.customization_of_SETTING_successfully_saved', { SETTING: _t(`app.admin.settings.${setting.name}`) }));
$scope.settings[$scope.name] = value;
},
function (error) {
if (error.status === 304) return;
if (error.status === 423) {
growl.error(_t('app.admin.settings.error_SETTING_locked', { SETTING: _t(`app.admin.settings.${setting.name}`) }));
return;
}
growl.error(_t('app.admin.settings.an_error_occurred_saving_the_setting'));
console.log(error);
}
);
};
}
});
}
]);

View File

@ -0,0 +1,55 @@
Application.Directives.directive('selectSetting', ['Setting', 'growl', '_t',
function (Setting, growl, _t) {
return ({
restrict: 'E',
scope: {
name: '@',
label: '@',
settings: '=',
classes: '@',
required: '<',
option1: '<',
option2: '<',
option3: '<',
option4: '<',
option5: '<'
},
templateUrl: '<%= asset_path "admin/settings/select.html" %>',
link ($scope, element, attributes) {
// The setting
$scope.setting = {
name: $scope.name,
value: $scope.settings[$scope.name]
};
/**
* Callback to save the setting value to the database
* @param setting {{value:*, name:string}} note that the value will be stringified
*/
$scope.save = function (setting) {
let { value } = setting;
Setting.update(
{ name: setting.name },
{ value },
function () {
growl.success(_t('app.admin.settings.customization_of_SETTING_successfully_saved', { SETTING: _t(`app.admin.settings.${setting.name}`) }));
$scope.settings[$scope.name] = value;
},
function (error) {
if (error.status === 304) return;
if (error.status === 423) {
growl.error(_t('app.admin.settings.error_SETTING_locked', { SETTING: _t(`app.admin.settings.${setting.name}`) }));
return;
}
growl.error(_t('app.admin.settings.an_error_occurred_saving_the_setting'));
console.log(error);
}
);
};
}
});
}
]);

View File

@ -0,0 +1,66 @@
Application.Directives.directive('textSetting', ['Setting', 'growl', '_t',
function (Setting, growl, _t) {
return ({
restrict: 'E',
scope: {
name: '@',
label: '@',
settings: '=',
classes: '@',
faIcon: '@',
placeholder: '@',
required: '<',
type: '@',
maxLength: '@',
minLength: '@',
readOnly: '<'
},
templateUrl: '<%= asset_path "admin/settings/text.html" %>',
link ($scope, element, attributes) {
// if type is not specified, use text as default
if (typeof $scope.type === 'undefined') {
$scope.type = 'text';
}
// The setting
$scope.setting = {
name: $scope.name,
value: $scope.settings[$scope.name]
};
$scope.$watch(`settings.${$scope.name}`, function (newValue, oldValue, scope) {
if (newValue !== oldValue) {
$scope.setting.value = newValue;
}
});
/**
* Callback to save the setting value to the database
* @param setting {{value:*, name:string}} note that the value will be stringified
*/
$scope.save = function (setting) {
let { value } = setting;
Setting.update(
{ name: setting.name },
{ value },
function () {
growl.success(_t('app.admin.settings.customization_of_SETTING_successfully_saved', { SETTING: _t(`app.admin.settings.${setting.name}`) }));
$scope.settings[$scope.name] = value;
},
function (error) {
if (error.status === 304) return;
if (error.status === 423) {
growl.error(_t('app.admin.settings.error_SETTING_locked', { SETTING: _t(`app.admin.settings.${setting.name}`) }));
return;
}
growl.error(_t('app.admin.settings.an_error_occurred_saving_the_setting'));
console.log(error);
}
);
};
}
});
}
]);

View File

@ -12,10 +12,11 @@ Application.Directives.directive('stripeForm', ['Payment', 'growl', '_t',
restrict: 'A',
scope: {
cartItems: '=',
onPaymentSuccess: '='
onPaymentSuccess: '=',
stripeKey: '@'
},
link: function($scope, element, attributes) {
const stripe = Stripe('<%= Rails.application.secrets.stripe_publishable_key %>');
const stripe = Stripe($scope.stripeKey);
const elements = stripe.elements();
const style = {

View File

@ -16,7 +16,7 @@ angular.module('application.router', ['ui.router'])
// abstract root parents states
// these states controls the access rights to the various routes inherited from them
return $stateProvider
$stateProvider
.state('app', {
abstract: true,
views: {
@ -36,14 +36,21 @@ angular.module('application.router', ['ui.router'])
resolve: {
logoFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'logo-file' }).$promise; }],
logoBlackFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'logo-black-file' }).$promise; }],
sharedTranslations: ['Translations', function (Translations) { return Translations.query(['app.shared', 'app.public.common']).$promise; }]
sharedTranslations: ['Translations', function (Translations) { return Translations.query(['app.shared', 'app.public.common']).$promise; }],
modulesPromise: ['Setting', function (Setting) { return Setting.query({ names: "['spaces_module', 'plans_module', 'invoicing_module', 'wallet_module']" }).$promise; }]
},
onEnter: ['$rootScope', 'logoFile', 'logoBlackFile', 'CSRF', function ($rootScope, logoFile, logoBlackFile, CSRF) {
onEnter: ['$rootScope', 'logoFile', 'logoBlackFile', 'modulesPromise', 'CSRF', function ($rootScope, logoFile, logoBlackFile, modulesPromise, CSRF) {
// Retrieve Anti-CSRF tokens from cookies
CSRF.setMetaTags();
// Application logo
$rootScope.logo = logoFile.custom_asset;
$rootScope.logoBlack = logoBlackFile.custom_asset;
$rootScope.modules = {
spaces: (modulesPromise.spaces_module === 'true'),
plans: (modulesPromise.plans_module === 'true'),
invoicing: (modulesPromise.invoicing_module === 'true'),
wallet: (modulesPromise.wallet_module === 'true'),
};
}]
})
.state('app.public', {
@ -98,7 +105,7 @@ angular.module('application.router', ['ui.router'])
}
},
resolve: {
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['home_content', 'home_blogpost']" }).$promise; }]
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['home_content', 'home_blogpost', 'spaces_module', 'feature_tour_display']" }).$promise; }]
}
})
.state('app.public.privacy', {
@ -126,6 +133,7 @@ angular.module('application.router', ['ui.router'])
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }],
cguFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'cgu-file' }).$promise; }],
memberPromise: ['Member', 'currentUser', function (Member, currentUser) { return Member.get({ id: currentUser.id }).$promise; }],
phoneRequiredPromise: ['Setting', function (Setting) { return Setting.get({ name: 'phone_required' }).$promise; }]
}
})
@ -157,6 +165,7 @@ angular.module('application.router', ['ui.router'])
resolve: {
groups: ['Group', function (Group) { return Group.query().$promise; }],
activeProviderPromise: ['AuthProvider', function (AuthProvider) { return AuthProvider.active().$promise; }],
phoneRequiredPromise: ['Setting', function (Setting) { return Setting.get({ name: 'phone_required' }).$promise; }]
}
})
.state('app.logged.dashboard.projects', {
@ -197,6 +206,7 @@ angular.module('application.router', ['ui.router'])
})
.state('app.logged.dashboard.wallet', {
url: '/wallet',
abstract: !Fablab.walletModule,
views: {
'main@': {
templateUrl: '<%= asset_path "dashboard/wallet.html" %>',
@ -247,7 +257,9 @@ angular.module('application.router', ['ui.router'])
resolve: {
themesPromise: ['Theme', function (Theme) { return Theme.query().$promise; }],
componentsPromise: ['Component', function (Component) { return Component.query().$promise; }],
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }]
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['openlab_app_id', 'openlab_default']" }).$promise; }],
openLabActive: ['Setting', function (Setting) { return Setting.isPresent({ name: 'openlab_app_secret' }).$promise; }],
}
})
.state('app.logged.projects_new', {
@ -259,7 +271,7 @@ angular.module('application.router', ['ui.router'])
}
},
resolve: {
allowedExtensions: ['Project', function (Project) { return Project.allowedExtensions().$promise; }]
allowedExtensions: ['Setting', function (Setting) { return Setting.get({ name: 'allowed_cad_extensions' }).$promise; }]
}
})
.state('app.public.projects_show', {
@ -271,7 +283,8 @@ angular.module('application.router', ['ui.router'])
}
},
resolve: {
projectPromise: ['$stateParams', 'Project', function ($stateParams, Project) { return Project.get({ id: $stateParams.id }).$promise; }]
projectPromise: ['$stateParams', 'Project', function ($stateParams, Project) { return Project.get({ id: $stateParams.id }).$promise; }],
shortnamePromise: ['Setting', function (Setting) { return Setting.get({ name: 'disqus_shortname' }).$promise; }]
}
})
.state('app.logged.projects_edit', {
@ -284,7 +297,7 @@ angular.module('application.router', ['ui.router'])
},
resolve: {
projectPromise: ['$stateParams', 'Project', function ($stateParams, Project) { return Project.get({ id: $stateParams.id }).$promise; }],
allowedExtensions: ['Project', function (Project) { return Project.allowedExtensions().$promise; }]
allowedExtensions: ['Setting', function (Setting) { return Setting.get({ name: 'allowed_cad_extensions' }).$promise; }]
}
})
@ -298,7 +311,8 @@ angular.module('application.router', ['ui.router'])
}
},
resolve: {
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }]
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['feature_tour_display']" }).$promise; }]
}
})
.state('app.admin.machines_new', {
@ -336,14 +350,9 @@ angular.module('application.router', ['ui.router'])
machinePromise: ['Machine', '$stateParams', function (Machine, $stateParams) { return Machine.get({ id: $stateParams.id }).$promise; }],
settingsPromise: ['Setting', function (Setting) {
return Setting.query({
names: `['machine_explications_alert', \
'booking_window_start', \
'booking_window_end', \
'booking_move_enable', \
'booking_move_delay', \
'booking_cancel_enable', \
'booking_cancel_delay', \
'subscription_explications_alert']`
names: `['machine_explications_alert', 'booking_window_start', 'booking_window_end', 'booking_move_enable', \
'booking_move_delay', 'booking_cancel_enable', 'booking_cancel_delay', 'subscription_explications_alert', \
'online_payment_module']`
}).$promise;
}]
}
@ -364,7 +373,7 @@ angular.module('application.router', ['ui.router'])
// spaces
.state('app.public.spaces_list', {
url: '/spaces',
abstract: Fablab.withoutSpaces,
abstract: !Fablab.spacesModule,
views: {
'main@': {
templateUrl: '<%= asset_path "spaces/index.html" %>',
@ -372,12 +381,13 @@ angular.module('application.router', ['ui.router'])
}
},
resolve: {
spacesPromise: ['Space', function (Space) { return Space.query().$promise; }]
spacesPromise: ['Space', function (Space) { return Space.query().$promise; }],
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['feature_tour_display']" }).$promise; }]
}
})
.state('app.admin.space_new', {
url: '/spaces/new',
abstract: Fablab.withoutSpaces,
abstract: !Fablab.spacesModule,
views: {
'main@': {
templateUrl: '<%= asset_path "spaces/new.html" %>',
@ -387,7 +397,7 @@ angular.module('application.router', ['ui.router'])
})
.state('app.public.space_show', {
url: '/spaces/:id',
abstract: Fablab.withoutSpaces,
abstract: !Fablab.spacesModule,
views: {
'main@': {
templateUrl: '<%= asset_path "spaces/show.html" %>',
@ -400,7 +410,7 @@ angular.module('application.router', ['ui.router'])
})
.state('app.admin.space_edit', {
url: '/spaces/:id/edit',
abstract: Fablab.withoutSpaces,
abstract: !Fablab.spacesModule,
views: {
'main@': {
templateUrl: '<%= asset_path "spaces/edit.html" %>',
@ -413,7 +423,7 @@ angular.module('application.router', ['ui.router'])
})
.state('app.logged.space_reserve', {
url: '/spaces/:id/reserve',
abstract: Fablab.withoutSpaces,
abstract: !Fablab.spacesModule,
views: {
'main@': {
templateUrl: '<%= asset_path "spaces/reserve.html" %>',
@ -427,14 +437,9 @@ angular.module('application.router', ['ui.router'])
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }],
settingsPromise: ['Setting', function (Setting) {
return Setting.query({
names: `['booking_window_start', \
'booking_window_end', \
'booking_move_enable', \
'booking_move_delay', \
'booking_cancel_enable', \
'booking_cancel_delay', \
'subscription_explications_alert', \
'space_explications_alert']` }).$promise;
names: `['booking_window_start', 'booking_window_end', 'booking_move_enable', 'booking_move_delay', \
'booking_cancel_enable', 'booking_cancel_delay', 'subscription_explications_alert', \
'space_explications_alert', 'online_payment_module']` }).$promise;
}]
}
})
@ -482,15 +487,9 @@ angular.module('application.router', ['ui.router'])
}],
settingsPromise: ['Setting', function (Setting) {
return Setting.query({
names: `['booking_window_start', \
'booking_window_end', \
'booking_move_enable', \
'booking_move_delay', \
'booking_cancel_enable', \
'booking_cancel_delay', \
'subscription_explications_alert', \
'training_explications_alert', \
'training_information_message']` }).$promise;
names: `['booking_window_start', 'booking_window_end', 'booking_move_enable', 'booking_move_delay', \
'booking_cancel_enable', 'booking_cancel_delay', 'subscription_explications_alert', \
'training_explications_alert', 'training_information_message', 'online_payment_module']` }).$promise;
}]
}
})
@ -508,7 +507,7 @@ angular.module('application.router', ['ui.router'])
// pricing
.state('app.public.plans', {
url: '/plans',
abstract: Fablab.withoutPlans,
abstract: !Fablab.plansModule,
views: {
'main@': {
templateUrl: '<%= asset_path "plans/index.html.erb" %>',
@ -518,7 +517,8 @@ angular.module('application.router', ['ui.router'])
resolve: {
subscriptionExplicationsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'subscription_explications_alert' }).$promise; }],
plansPromise: ['Plan', function (Plan) { return Plan.query().$promise; }],
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }]
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }],
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['online_payment_module']" }).$promise; }]
}
})
@ -534,7 +534,8 @@ angular.module('application.router', ['ui.router'])
resolve: {
categoriesPromise: ['Category', function (Category) { return Category.query().$promise; }],
themesPromise: ['EventTheme', function (EventTheme) { return EventTheme.query().$promise; }],
ageRangesPromise: ['AgeRange', function (AgeRange) { return AgeRange.query().$promise; }]
ageRangesPromise: ['AgeRange', function (AgeRange) { return AgeRange.query().$promise; }],
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['online_payment_module']" }).$promise; }]
}
})
.state('app.public.events_show', {
@ -586,7 +587,8 @@ angular.module('application.router', ['ui.router'])
bookingWindowEnd: ['Setting', function (Setting) { return Setting.get({ name: 'booking_window_end' }).$promise; }],
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
plansPromise: ['Plan', function (Plan) { return Plan.query().$promise; }],
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }]
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }],
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['slot_duration', 'events_in_calendar', 'feature_tour_display']" }).$promise; }]
}
})
.state('app.admin.calendar.icalendar', {
@ -602,19 +604,23 @@ angular.module('application.router', ['ui.router'])
}
})
// project's elements
.state('app.admin.project_elements', {
url: '/admin/project_elements',
// project's settings
.state('app.admin.projects', {
url: '/admin/projects',
views: {
'main@': {
templateUrl: '<%= asset_path "admin/project_elements/index.html.erb" %>',
controller: 'ProjectElementsController'
templateUrl: '<%= asset_path "admin/projects/index.html.erb" %>',
controller: 'AdminProjectsController'
}
},
resolve: {
componentsPromise: ['Component', function (Component) { return Component.query().$promise; }],
licencesPromise: ['Licence', function (Licence) { return Licence.query().$promise; }],
themesPromise: ['Theme', function (Theme) { return Theme.query().$promise; }]
themesPromise: ['Theme', function (Theme) { return Theme.query().$promise; }],
settingsPromise: ['Setting', function (Setting) {
return Setting.query({ names: "['feature_tour_display', 'disqus_shortname', 'allowed_cad_extensions', \
'allowed_cad_mime_types', 'openlab_app_id', 'openlab_app_secret', 'openlab_default']" }).$promise;
}]
}
})
.state('app.admin.manage_abuses', {
@ -641,7 +647,8 @@ angular.module('application.router', ['ui.router'])
},
resolve: {
trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }],
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }]
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['feature_tour_display']" }).$promise; }]
}
})
.state('app.admin.trainings_new', {
@ -683,7 +690,8 @@ angular.module('application.router', ['ui.router'])
categoriesPromise: ['Category', function (Category) { return Category.query().$promise; }],
themesPromise: ['EventTheme', function (EventTheme) { return EventTheme.query().$promise; }],
ageRangesPromise: ['AgeRange', function (AgeRange) { return AgeRange.query().$promise; }],
priceCategoriesPromise: ['PriceCategory', function (PriceCategory) { return PriceCategory.query().$promise; }]
priceCategoriesPromise: ['PriceCategory', function (PriceCategory) { return PriceCategory.query().$promise; }],
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['feature_tour_display']" }).$promise; }]
}
})
.state('app.admin.events_new', {
@ -752,7 +760,8 @@ angular.module('application.router', ['ui.router'])
couponsPromise: ['Coupon', function (Coupon) { return Coupon.query({ page: 1, filter: 'all' }).$promise; }],
spacesPromise: ['Space', function (Space) { return Space.query().$promise; }],
spacesPricesPromise: ['Price', function (Price) { return Price.query({ priceable_type: 'Space', plan_id: 'null' }).$promise; }],
spacesCreditsPromise: ['Credit', function (Credit) { return Credit.query({ creditable_type: 'Space' }).$promise; }]
spacesCreditsPromise: ['Credit', function (Credit) { return Credit.query({ creditable_type: 'Space' }).$promise; }],
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['feature_tour_display', 'slot_duration']" }).$promise; }]
}
})
@ -827,12 +836,15 @@ angular.module('application.router', ['ui.router'])
return Setting.query({
names: `['invoice_legals', 'invoice_text', 'invoice_VAT-rate', 'invoice_VAT-active', 'invoice_order-nb', 'invoice_code-value', \
'invoice_code-active', 'invoice_reference', 'invoice_logo', 'accounting_journal_code', 'accounting_card_client_code', \
'accounting_card_client_label', 'accounting_wallet_client_code', 'accounting_wallet_client_label', \
'accounting_card_client_label', 'accounting_wallet_client_code', 'accounting_wallet_client_label', 'invoicing_module', \
'accounting_other_client_code', 'accounting_other_client_label', 'accounting_wallet_code', 'accounting_wallet_label', \
'accounting_VAT_code', 'accounting_VAT_label', 'accounting_subscription_code', 'accounting_subscription_label', \
'accounting_Machine_code', 'accounting_Machine_label', 'accounting_Training_code', 'accounting_Training_label', \
'accounting_Event_code', 'accounting_Event_label', 'accounting_Space_code', 'accounting_Space_label']` }).$promise;
'accounting_Event_code', 'accounting_Event_label', 'accounting_Space_code', 'accounting_Space_label', \
'feature_tour_display', 'online_payment_module', 'stripe_public_key', 'stripe_currency', 'invoice_prefix']` }).$promise;
}],
stripeSecretKey: ['Setting', function (Setting) { return Setting.isPresent({ name: 'stripe_secret_key' }).$promise; }],
onlinePaymentStatus: ['Payment', function (Payment) { return Payment.onlinePaymentStatus().$promise; }],
invoices: [ 'Invoice', function (Invoice) {
return Invoice.list({
query: { number: '', customer: '', date: null, order_by: '-reference', page: 1, size: 20 }
@ -870,7 +882,8 @@ angular.module('application.router', ['ui.router'])
managersPromise: ['User', function (User) { return User.query({ role: 'manager' }).$promise; }],
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }],
tagsPromise: ['Tag', function (Tag) { return Tag.query().$promise; }],
authProvidersPromise: ['AuthProvider', function (AuthProvider) { return AuthProvider.query().$promise; }]
authProvidersPromise: ['AuthProvider', function (AuthProvider) { return AuthProvider.query().$promise; }],
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['feature_tour_display']" }).$promise; }]
}
})
.state('app.admin.members_new', {
@ -880,6 +893,9 @@ angular.module('application.router', ['ui.router'])
templateUrl: '<%= asset_path "admin/members/new.html" %>',
controller: 'NewMemberController'
}
},
resolve: {
phoneRequiredPromise: ['Setting', function (Setting) { return Setting.get({ name: 'phone_required' }).$promise; }]
}
})
.state('app.admin.members_import', {
@ -919,7 +935,8 @@ angular.module('application.router', ['ui.router'])
activeProviderPromise: ['AuthProvider', function (AuthProvider) { return AuthProvider.active().$promise; }],
walletPromise: ['Wallet', '$stateParams', function (Wallet, $stateParams) { return Wallet.getWalletByUser({ user_id: $stateParams.id }).$promise; }],
transactionsPromise: ['Wallet', 'walletPromise', function (Wallet, walletPromise) { return Wallet.transactions({ id: walletPromise.id }).$promise; }],
tagsPromise: ['Tag', function (Tag) { return Tag.query().$promise; }]
tagsPromise: ['Tag', function (Tag) { return Tag.query().$promise; }],
phoneRequiredPromise: ['Setting', function (Setting) { return Setting.get({ name: 'phone_required' }).$promise; }]
}
})
.state('app.admin.admins_new', {
@ -984,7 +1001,8 @@ angular.module('application.router', ['ui.router'])
},
resolve: {
membersPromise: ['Member', function (Member) { return Member.mapping().$promise; }],
statisticsPromise: ['Statistics', function (Statistics) { return Statistics.query().$promise; }]
statisticsPromise: ['Statistics', function (Statistics) { return Statistics.query().$promise; }],
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['feature_tour_display']" }).$promise; }]
}
})
.state('app.admin.stats_graphs', {
@ -1009,17 +1027,17 @@ angular.module('application.router', ['ui.router'])
resolve: {
settingsPromise: ['Setting', function (Setting) {
return Setting.query({
names: `['twitter_name', 'about_title', 'about_body', \
'privacy_body', 'privacy_dpo', 'about_contacts', \
'home_blogpost', 'machine_explications_alert', 'training_explications_alert', \
names: `['twitter_name', 'about_title', 'about_body', 'tracking_id', 'facebook_app_id', 'email_from', \
'privacy_body', 'privacy_dpo', 'about_contacts', 'book_overlapping_slots', 'invoicing_module', \
'home_blogpost', 'machine_explications_alert', 'training_explications_alert', 'slot_duration', \
'training_information_message', 'subscription_explications_alert', 'event_explications_alert', \
'space_explications_alert', 'booking_window_start', 'booking_window_end', \
'booking_move_enable', 'booking_move_delay', 'booking_cancel_enable', \
'booking_cancel_delay', 'main_color', 'secondary_color', \
'fablab_name', 'name_genre', 'reminder_enable', \
'reminder_delay', 'visibility_yearly', 'visibility_others', \
'space_explications_alert', 'booking_window_start', 'booking_window_end', 'events_in_calendar', \
'booking_move_enable', 'booking_move_delay', 'booking_cancel_enable', 'feature_tour_display', \
'booking_cancel_delay', 'main_color', 'secondary_color', 'spaces_module', 'twitter_analytics', \
'fablab_name', 'name_genre', 'reminder_enable', 'plans_module', 'confirmation_required', \
'reminder_delay', 'visibility_yearly', 'visibility_others', 'wallet_module', \
'display_name_enable', 'machines_sort_by', 'fab_analytics', \
'link_name', 'home_content', 'home_css']` }).$promise;
'link_name', 'home_content', 'home_css', 'phone_required']` }).$promise;
}],
privacyDraftsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'privacy_draft', history: true }).$promise; }],
cguFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'cgu-file' }).$promise; }],
@ -1039,7 +1057,8 @@ angular.module('application.router', ['ui.router'])
}
},
resolve: {
clientsPromise: ['OpenAPIClient', function (OpenAPIClient) { return OpenAPIClient.query().$promise; }]
clientsPromise: ['OpenAPIClient', function (OpenAPIClient) { return OpenAPIClient.query().$promise; }],
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['feature_tour_display']" }).$promise; }]
}
});
}

View File

@ -12,7 +12,7 @@ Application.Services.factory('Help', ['$rootScope', '$uibModal', '$state', 'Auth
'app.admin.invoices': 'invoices',
'app.admin.pricing': 'pricing',
'app.admin.events': 'events',
'app.admin.project_elements': 'project-elements',
'app.admin.projects': 'projects',
'app.admin.statistics': 'statistics',
'app.admin.settings': 'settings',
'app.admin.open_api_clients': 'open-api'

View File

@ -1,6 +1,6 @@
'use strict';
Application.Services.factory('Member', ['$resource', '$q', function ($resource, $q) {
Application.Services.factory('Member', ['$resource', 'Setting', function ($resource, Setting) {
return $resource('/api/members/:id',
{ id: '@id' }, {
update: {
@ -37,11 +37,13 @@ Application.Services.factory('Member', ['$resource', '$q', function ($resource,
params: { id: '@id' },
interceptor: {
response: function (response) {
if (Fablab.featureTourDisplay === 'session') {
Fablab.sessionTours.push(response.data.tours[0]);
return { tours: Fablab.sessionTours };
}
return response.data;
return Setting.query({ names: "['feature_tour_display']" }).$promise.then((settings) => {
if (settings.feature_tour_display === 'session') {
Fablab.sessionTours.push(response.data.tours[0]);
return { tours: Fablab.sessionTours };
}
return response.data;
});
}
}
},

View File

@ -7,6 +7,10 @@ Application.Services.factory('Payment', ['$resource', function ($resource) {
method: 'POST',
url: '/api/payments/confirm_payment',
isArray: false
},
onlinePaymentStatus: {
method: 'GET',
url: '/api/payments/online_payment_status'
}
}
);

View File

@ -12,11 +12,6 @@ Application.Services.factory('Project', ['$resource', function ($resource) {
method: 'GET',
url: '/api/projects/search',
isArray: false
},
allowedExtensions: {
method: 'GET',
url: '/api/projects/allowed_extensions',
isArray: true
}
}
);

View File

@ -20,6 +20,11 @@ Application.Services.factory('Setting', ['$resource', function ($resource) {
url: '/api/settings/reset/:name',
params: { name: '@name' },
method: 'PUT'
},
isPresent: {
url: '/api/settings/is_present/:name',
params: { name: '@name' },
method: 'GET'
}
}
);

View File

@ -8,6 +8,28 @@
color: red;
}
.invoice-file {
text-align: center;
line-height: 4em;
margin-top: 2em;
.fa-file-pdf-o {
font-size: 4em;
vertical-align: middle;
}
.filename {
font-size: 1.1em;
vertical-align: middle;
margin-left: 1em;
.prefix:hover {
background-color: $yellow;
overflow-x: hidden;
}
}
}
.invoice-placeholder {
width: 80%;
max-width: 800px;

View File

@ -67,4 +67,10 @@
}
}
}
.section-separator {
background: radial-gradient(#dddddd, transparent);
height: 1px;
margin: 24px 33% 12px 33%;
}
}

View File

@ -38,7 +38,7 @@
<div class="legends">
<span class="calendar-legend text-sm border-formation" translate>{{ 'app.admin.calendar.trainings' }}</span><br>
<span class="calendar-legend text-sm border-machine" translate>{{ 'app.admin.calendar.machines' }}</span><br>
<span class="calendar-legend text-sm border-space" ng-hide="fablabWithoutSpaces" translate>{{ 'app.admin.calendar.spaces' }}</span>
<span class="calendar-legend text-sm border-space" ng-show="modules.spaces" translate>{{ 'app.admin.calendar.spaces' }}</span>
<span class="calendar-legend text-sm border-event" ng-show="eventsInCalendar" translate>{{ 'app.admin.calendar.events' }}</span>
</div>
</div>

View File

@ -18,7 +18,7 @@
<span translate>{{ 'app.admin.calendar.machine' }}</span>
</label>
</div>
<div class="radio" ng-hide="fablabWithoutSpaces">
<div class="radio" ng-show="modules.spaces">
<label>
<input type="radio" id="space" name="available_type" value="space" ng-model="availability.available_type" ng-disabled="spaces.length === 0">
<span translate>{{ 'app.admin.calendar.space' }}</span>

View File

@ -53,8 +53,7 @@
<div class="form-group" ng-class="{'has-error': couponForm['coupon[amount_off]'].$dirty && couponForm['coupon[amount_off]'].$invalid}" ng-show="coupon.type == 'amount_off'">
<label for="coupon[amount_off]">{{ 'app.shared.coupon.amount_off' | translate }} *</label>
<div class="input-group">
²<div class="input-group">
<span class="input-group-addon">{{currencySymbol}}</span>
<input type="number" id="coupon[amount_off]"
name="coupon[amount_off]"

View File

@ -30,7 +30,7 @@
<div class="row">
<div class="col-md-12" ng-if="isAuthorized('admin')">
<uib-tabset justified="true" active="tabs.active">
<uib-tab heading="{{ 'app.admin.invoices.invoices_list' | translate }}" ng-hide="fablabWithoutInvoices" index="0">
<uib-tab heading="{{ 'app.admin.invoices.invoices_list' | translate }}" ng-show="modules.invoicing" index="0">
<ng-include src="'<%= asset_path "admin/invoices/list.html" %>'"></ng-include>
</uib-tab>
@ -41,6 +41,10 @@
<uib-tab heading="{{ 'app.admin.invoices.accounting_codes' | translate }}" index="2" class="accounting-codes-tab">
<ng-include src="'<%= asset_path "admin/invoices/codes.html" %>'"></ng-include>
</uib-tab>
<uib-tab heading="{{ 'app.admin.invoices.payment.payment_settings' | translate }}" index="3" class="payment-settings">
<ng-include src="'<%= asset_path "admin/invoices/payment.html" %>'"></ng-include>
</uib-tab>
</uib-tabset>
</div>

View File

@ -0,0 +1,114 @@
<div class="panel panel-default m-t-md">
<div class="panel-heading">
<span class="font-sbold" translate>{{ 'app.admin.invoices.payment.payment_settings' }}</span>
</div>
<div class="panel-body">
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.invoices.payment.online_payment' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.invoices.payment.online_payment_info_html' | translate"></p>
<boolean-setting name="online_payment_module"
settings="allSettings"
label="app.admin.invoices.payment.enable_online_payment"
classes="m-l"
on-before-save="requireStripeKeys"
fa-icon="fa-font">
</boolean-setting>
</div>
<div class="row m-t" ng-show="allSettings.online_payment_module === 'true'">
<h3 class="m-l" translate>{{ 'app.admin.invoices.payment.stripe_keys' }}</h3>
<div class="col-md-4 m-l">
<label for="stripe_public_key" class="control-label">{{ 'app.admin.invoices.payment.public_key' | translate }}</label>
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-info"></i></span>
<input type="text"
class="form-control"
id="stripe_public_key"
ng-model="allSettings.stripe_public_key"
readonly>
</div>
</div>
<div class="col-md-4 col-md-offset-1">
<label for="stripe_secret_key" class="control-label">{{ 'app.admin.invoices.payment.secret_key' | translate }}</label>
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-key"></i></span>
<input type="password"
class="form-control"
id="stripe_secret_key"
ng-model="stripeSecretKey"
readonly>
</div>
</div>
<div class="col-md-1">
<button class="btn btn-default m-t-lg" ng-click="requireStripeKeys(allSettings.online_payment_module)" translate>{{ 'app.admin.invoices.payment.edit_keys' }}</button>
</div>
</div>
<div class="row m-t" ng-show="allSettings.online_payment_module === 'true'">
<h3 class="m-l" translate>{{ 'app.admin.invoices.payment.currency' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.invoices.payment.currency_info_html' | translate"></p>
<p class="alert alert-danger m-h-md" ng-bind-html="'app.admin.invoices.payment.currency_alert_html' | translate"></p>
<div class="col-md-4 m-l">
<text-setting name="stripe_currency"
settings="allSettings"
label="app.admin.invoices.payment.stripe_currency"
fa-icon="fa-money"
placeholder="XXX"
required="true"
min-length="3"
max-length="3"
read-only="onlinePaymentStatus">
</text-setting>
</div>
</div>
</div>
</div>
<script type="text/ng-template" id="stripeKeys.html">
<div>
<div class="modal-header">
<h3 class="modal-title" translate>{{ 'app.admin.invoices.payment.stripe_keys' }}</h3>
</div>
<div class="modal-body">
<div class="alert alert-info" ng-bind-html="'app.admin.invoices.payment.stripe_keys_info_html' | translate"></div>
<form name="stripeKeysForm">
<div class="row m-md">
<label for="stripe_public_key" class="control-label">{{ 'app.admin.invoices.payment.public_key' | translate }} *</label>
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-info"></i></span>
<input type="text"
class="form-control"
id="stripe_public_key"
ng-model="publicKey"
ng-model-options='{ debounce: 200 }'
ng-change='testPublicKey()'
required>
<span class="input-group-addon" ng-class="{'label-success': publicKeyStatus, 'label-danger text-white': !publicKeyStatus}" ng-show="publicKeyStatus !== undefined && publicKey">
<i class="fa fa-times" ng-show="!publicKeyStatus"></i>
<i class="fa fa-check" ng-show="publicKeyStatus"></i>
</span>
</div>
</div>
<div class="row m-md">
<label for="stripe_secret_key" class="control-label">{{ 'app.admin.invoices.payment.secret_key' | translate }} *</label>
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-key"></i></span>
<input type="text"
class="form-control"
id="stripe_secret_key"
ng-model="secretKey"
ng-model-options='{ debounce: 200 }'
ng-change='testSecretKey()'
required>
<span class="input-group-addon" ng-class="{'label-success': secretKeyStatus, 'label-danger text-white': !secretKeyStatus}" ng-show="secretKeyStatus !== undefined && secretKey">
<i class="fa fa-times" ng-show="!secretKeyStatus"></i>
<i class="fa fa-check" ng-show="secretKeyStatus"></i>
</span>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-warning" ng-click="ok()" ng-disabled="stripeKeysForm.$invalid" translate>{{ 'app.shared.buttons.confirm' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
</div>
</div>
</script>

View File

@ -1,4 +1,4 @@
<div class="alert alert-warning p-md m-t" role="alert" ng-show="fablabWithoutInvoices">
<div class="alert alert-warning p-md m-t" role="alert" ng-hide="modules.invoicing">
<i class="fa fa-warning m-r"></i>
<span translate>{{ 'app.admin.invoices.warning_invoices_disabled' }}</span>
</div>
@ -87,6 +87,11 @@
ng-blur="legalsEditEnd($event)">
</div>
</form>
<div class="invoice-file">
<h3 class="m-l" translate>{{ 'app.admin.invoices.filename' }}</h3>
<i class="fa fa-file-pdf-o" aria-hidden="true"></i>
<span class="filename"><span class="prefix" ng-click="openEditPrefix()">{{file.prefix}}</span>-{{file.nextId}}_{{file.date}}.pdf</span>
</div>
<script type="text/ng-template" id="editReference.html">
@ -308,3 +313,27 @@
</div>
</div>
</script>
<script type="text/ng-template" id="editPrefix.html">
<div class="custom-invoice">
<div class="modal-header">
<h3 class="modal-title" translate>{{ 'app.admin.invoices.filename' }}</h3>
</div>
<div class="modal-body">
<p class="alert alert-info m-h-md" translate>
{{ 'app.admin.invoices.prefix_info' }}
</p>
<div>
<div class="model">
<label for="prefix" translate>{{ 'app.admin.invoices.prefix' }}</label>
<input type="text" id="prefix" class="form-control" ng-model="model">
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
</div>
</div>
</script>

View File

@ -60,7 +60,7 @@
</form>
</uib-tab>
<uib-tab heading="{{ 'app.admin.members_edit.subscription' | translate }}" ng-if="!fablabWithoutPlans">
<uib-tab heading="{{ 'app.admin.members_edit.subscription' | translate }}" ng-if="modules.plans">
<section class="panel panel-default bg-light m-lg">
@ -192,7 +192,7 @@
</div>
</uib-tab>
<uib-tab heading="{{ 'app.admin.members_edit.invoices' | translate }}" ng-hide="fablabWithoutInvoices">
<uib-tab heading="{{ 'app.admin.members_edit.invoices' | translate }}" ng-show="modules.invoicing">
<div class="col-md-12 m m-t-lg">
@ -229,7 +229,7 @@
</div>
</uib-tab>
<uib-tab heading="{{ 'app.admin.members_edit.wallet' | translate }}" ng-hide="fablabWithoutWallet">
<uib-tab heading="{{ 'app.admin.members_edit.wallet' | translate }}" ng-show="modules.wallet">
<div class="col-md-12 m m-t-lg">
<ng-include src="'<%= asset_path "wallet/show.html" %>'"></ng-include>

View File

@ -25,7 +25,7 @@
<a class="btn btn-default" ng-href="api/members/export_members.xlsx" target="export-frame" ng-click="alertExport('members')">
<i class="fa fa-file-excel-o"></i> {{ 'app.admin.members.members' | translate }}
</a>
<a class="btn btn-default" ng-href="api/members/export_subscriptions.xlsx" target="export-frame" ng-if="!fablabWithoutPlans" ng-click="alertExport('subscriptions')">
<a class="btn btn-default" ng-href="api/members/export_subscriptions.xlsx" target="export-frame" ng-if="modules.plans" ng-click="alertExport('subscriptions')">
<i class="fa fa-file-excel-o"></i> {{ 'app.admin.members.subscriptions' | translate }}
</a>
<a class="btn btn-default" ng-href="api/members/export_reservations.xlsx" target="export-frame" ng-click="alertExport('reservations')">

View File

@ -130,7 +130,7 @@
<span class="input-group-addon btn btn-default btn-file"><span class="fileinput-new" translate>{{ 'app.shared.plan.attach_an_information_sheet' }}</span>
<span class="fileinput-exists" translate>{{ 'app.shared.buttons.change' }}</span><input type="file"
name="plan[plan_file_attributes][attachment]"
accept="image/*, application/pdf"></span>
accept="image/jpeg,image/gif,image/png,application/pdf"></span>
<a class="input-group-addon btn btn-danger fileinput-exists" data-dismiss="fileinput" ng-click="deleteFile(file || plan.plan_file_attributes)"><i class="fa fa-trash-o"></i></a>
</div>

View File

@ -73,8 +73,8 @@
</tbody>
</table>
<h3 ng-hide="fablabWithoutSpaces" translate>{{ 'app.admin.plans.edit.spaces' }}</h3>
<table class="table" ng-hide="fablabWithoutSpaces">
<h3 ng-show="modules.spaces" translate>{{ 'app.admin.plans.edit.spaces' }}</h3>
<table class="table" ng-show="modules.spaces">
<thead>
<th translate>{{ 'app.admin.plans.edit.space' }}</th>
<th translate>{{ 'app.admin.plans.edit.hourly_rate' }}</th>

View File

@ -96,11 +96,11 @@
</tbody>
</table>
<h2 ng-hide="fablabWithoutSpaces" class="m-t-lg" translate>{{ 'app.admin.pricing.spaces' }}</h2>
<div ng-hide="fablabWithoutSpaces" class="btn-group m-t-md m-b-md">
<h2 ng-show="modules.spaces" class="m-t-lg" translate>{{ 'app.admin.pricing.spaces' }}</h2>
<div ng-show="modules.spaces" class="btn-group m-t-md m-b-md">
<button type="button" class="btn btn-warning" ng-click="addSpaceCredit($event)" translate>{{ 'app.admin.pricing.add_a_space_credit' }}</button>
</div>
<table ng-hide="fablabWithoutSpaces" class="table">
<table ng-show="modules.spaces" class="table">
<thead>
<tr>
<th style="width:20%" translate>{{ 'app.admin.pricing.space' }}</th>

View File

@ -39,7 +39,7 @@
<ng-include src="'<%= asset_path "admin/pricing/machine_hours.html" %>'"></ng-include>
</uib-tab>
<uib-tab heading="{{ 'app.admin.pricing.spaces' | translate }}" ng-hide="fablabWithoutSpaces" index="3" class="spaces-tab">
<uib-tab heading="{{ 'app.admin.pricing.spaces' | translate }}" ng-show="modules.spaces" index="3" class="spaces-tab">
<ng-include src="'<%= asset_path "admin/pricing/spaces.html" %>'"></ng-include>
</uib-tab>

View File

@ -1,9 +1,8 @@
<h2 translate>{{ 'app.admin.pricing.list_of_the_subscription_plans' }}</h2>
<div ng-show="fablabWithoutPlans" class="alert alert-warning m-t">
{{ 'app.admin.pricing.beware_the_subscriptions_are_disabled_on_this_application' | translate }}
{{ 'app.admin.pricing.you_can_create_some_but_they_wont_be_available_until_the_project_is_redeployed_by_the_server_manager' | translate }}
<br>{{ 'app.admin.pricing.for_safety_reasons_please_dont_create_subscriptions_if_you_dont_want_intend_to_use_them_later' | translate }}
<div ng-hide="modules.plans"
class="alert alert-warning m-t"
ng-bind-html="'app.admin.pricing.disabled_plans_info_html' | translate">
</div>
<div class="m-t-lg">

View File

@ -7,20 +7,20 @@
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
<section class="heading-title">
<h1 translate>{{ 'app.admin.project_elements.projects_elements_management' }}</h1>
<h1 translate>{{ 'app.admin.projects.projects_settings' }}</h1>
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
<section class="heading-actions wrapper">
<a class="btn btn-ng btn-warning b-2x rounded m-t-sm upper text-sm abuses-button" ui-sref="app.admin.manage_abuses" role="button" translate>{{ 'app.admin.project_elements.manage_abuses' }}</a>
<a class="btn btn-ng btn-warning b-2x rounded m-t-sm upper text-sm abuses-button" ui-sref="app.admin.manage_abuses" role="button" translate>{{ 'app.admin.projects.manage_abuses' }}</a>
</section>
</div>
</div>
</section>
<section class="m-lg project-elements"
ui-tour="project-elements"
<section class="m-lg projects"
ui-tour="projects"
ui-tour-backdrop="true"
ui-tour-template-url="'<%= asset_path "shared/tour-step-template.html" %>'"
ui-tour-use-hotkeys="true"
@ -29,15 +29,18 @@
<div class="row">
<div class="col-md-12">
<uib-tabset justified="true">
<uib-tab heading="{{ 'app.admin.project_elements.materials' | translate }}">
<ng-include src="'<%= asset_path "admin/project_elements/materials.html" %>'"></ng-include>
<uib-tabset justified="true" active="tabs.active">
<uib-tab heading="{{ 'app.admin.projects.materials' | translate }}" index="0">
<ng-include src="'<%= asset_path "admin/projects/materials.html" %>'"></ng-include>
</uib-tab>
<uib-tab heading="{{ 'app.admin.project_elements.themes' | translate }}">
<ng-include src="'<%= asset_path "admin/project_elements/themes.html" %>'"></ng-include>
<uib-tab heading="{{ 'app.admin.projects.themes' | translate }}" index="1">
<ng-include src="'<%= asset_path "admin/projects/themes.html" %>'"></ng-include>
</uib-tab>
<uib-tab heading="{{ 'app.admin.project_elements.licences' | translate }}">
<ng-include src="'<%= asset_path "admin/project_elements/licences.html" %>'"></ng-include>
<uib-tab heading="{{ 'app.admin.projects.licences' | translate }}" index="2">
<ng-include src="'<%= asset_path "admin/projects/licences.html" %>'"></ng-include>
</uib-tab>
<uib-tab heading="{{ 'app.admin.projects.settings.title' | translate }}" index="3" class="settings-tab">
<ng-include src="'<%= asset_path "admin/projects/settings.html" %>'"></ng-include>
</uib-tab>
</uib-tabset>
</div>

View File

@ -1,10 +1,10 @@
<button type="button" class="btn btn-warning m-t m-b" ng-click="addLicence()" translate>{{ 'app.admin.project_elements.add_a_new_licence' }}</button>
<button type="button" class="btn btn-warning m-t m-b" ng-click="addLicence()" translate>{{ 'app.admin.projects.add_a_new_licence' }}</button>
<table class="table">
<thead>
<tr>
<th style="width:30%" translate>{{ 'app.admin.project_elements.name' }}</th>
<th style="width:50%" class="hidden-xs" translate>{{ 'app.admin.project_elements.description' }}</th>
<th style="width:30%" translate>{{ 'app.admin.projects.name' }}</th>
<th style="width:50%" class="hidden-xs" translate>{{ 'app.admin.projects.description' }}</th>
<th style="width:20%"></th>
</tr>
</thead>

View File

@ -1,9 +1,9 @@
<button type="button" class="btn btn-warning m-b m-t" ng-click="addComponent()" translate>{{ 'app.admin.project_elements.add_a_material' }}</button>
<button type="button" class="btn btn-warning m-b m-t" ng-click="addComponent()" translate>{{ 'app.admin.projects.add_a_material' }}</button>
<table class="table">
<thead>
<tr>
<th style="width:80%" translate>{{ 'app.admin.project_elements.name' }}</th>
<th style="width:80%" translate>{{ 'app.admin.projects.name' }}</th>
<th style="width:20%"></th>
</tr>
</thead>

View File

@ -0,0 +1,98 @@
<div class="panel panel-default m-t-lg">
<div class="panel-heading">
<span class="font-sbold" translate>{{ 'app.admin.projects.settings.comments' }}</span>
</div>
<div class="panel-body">
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.projects.settings.disqus' }}</h3>
<p class="alert alert-warning m-h-md" translate>{{ 'app.admin.projects.settings.disqus_info' }}</p>
<div class="col-md-4">
<text-setting name="disqus_shortname"
settings="allSettings"
label="app.admin.projects.settings.shortname"
fa-icon="fa-comments"
placeholder="my-forum">
</text-setting>
</div>
</div>
</div>
</div>
<div class="panel panel-default m-t-lg">
<div class="panel-heading">
<span class="font-sbold" translate>{{ 'app.admin.projects.settings.cad_files' }}</span>
</div>
<div class="panel-body">
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.projects.settings.validation' }}</h3>
<p class="alert alert-warning m-h-md" translate>{{ 'app.admin.projects.settings.validation_info' }}</p>
<div class="col-md-5">
<select-multiple-setting name="allowed_cad_extensions"
settings="allSettings"
label="app.admin.projects.settings.extensions"
title-new="app.admin.projects.settings.new_extension"
description-new="app.admin.projects.settings.new_ext_info_html"
before-add="removeInitialDot">
</select-multiple-setting>
</div>
<div class="col-md-5 col-md-offset-2">
<select-multiple-setting name="allowed_cad_mime_types"
settings="allSettings"
label="app.admin.projects.settings.mime_types"
title-new="app.admin.projects.settings.new_mime_type"
description-new="app.admin.projects.settings.new_type_info_html"
before-add="lower">
</select-multiple-setting>
</div>
<form name="mimeTestForm" class="col-md-6 m-t-lg" ng-upload="onTestFileComplete(content)" upload-options-enable-rails-csrf="true" action="/api/files/mime_type">
<label for="testFile" class="control-label" translate>{{ 'app.admin.projects.settings.test_file' }}</label>
<div class="fileinput input-group" data-provides="fileinput" ng-class="fileinputClass()">
<div class="form-control" data-trigger="fileinput">
<i class="glyphicon glyphicon-file fileinput-exists"></i> <span class="fileinput-filename">{{file.attachment}}</span>
</div>
<span class="input-group-addon btn btn-default btn-file">
<span class="fileinput-new" translate>{{ 'app.admin.projects.settings.set_a_file' }}</span>
<span class="fileinput-exists" translate>{{ 'app.shared.buttons.change' }}</span>
<input type="file" id="testFile" name="attachment" accept="*/*" required>
</span>
</div>
<input type="submit" class="btn btn-warning" ng-disabled="mimeTestForm.$invalid || $isUploading">
</form>
</div>
</div>
</div>
<div class="panel panel-default m-t-lg">
<div class="panel-heading">
<span class="font-sbold" translate>{{ 'app.admin.projects.settings.projects_sharing' }}</span>
</div>
<div class="panel-body">
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.projects.settings.open_lab_projects' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.projects.settings.open_lab_info_html' | translate"></p>
<div class="col-md-4">
<text-setting name="openlab_app_id"
settings="allSettings"
label="app.admin.projects.settings.open_lab_app_id"
fa-icon="fa-info">
</text-setting>
</div>
<div class="col-md-4 col-md-offset-2">
<text-setting name="openlab_app_secret"
settings="allSettings"
label="app.admin.projects.settings.open_lab_app_secret"
fa-icon="fa-key">
</text-setting>
</div>
</div>
<div class="row m-t" ng-show="allSettings.openlab_app_secret">
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.projects.settings.openlab_default_info_html' | translate"></p>
<boolean-setting name="openlab_default"
settings="allSettings"
label="app.admin.projects.settings.default_to_openlab"
classes="m-l"
yes-label="app.shared.buttons.yes"
no-label="app.shared.buttons.no"></boolean-setting>
</div>
</div>
</div>

View File

@ -1,9 +1,9 @@
<button type="button" class="btn btn-warning m-t m-b" ng-click="addTheme()" translate>{{ 'app.admin.project_elements.add_a_new_theme' }}</button>
<button type="button" class="btn btn-warning m-t m-b" ng-click="addTheme()" translate>{{ 'app.admin.projects.add_a_new_theme' }}</button>
<table class="table">
<thead>
<tr>
<th style="width:80%" translate>{{ 'app.admin.project_elements.name' }}</th>
<th style="width:80%" translate>{{ 'app.admin.projects.name' }}</th>
<th style="width:20%"></th>
</tr>
</thead>

View File

@ -4,16 +4,11 @@
</div>
<div class="panel-body">
<div class="col-md-4">
<form role="form" novalidate>
<label for="linkName" class="control-label m-r" translate>{{ 'app.admin.settings.link_to_about' }}</label>
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-link"></i></div>
<input type="text" id="linkName" ng-model="linkName.value" class="form-control" placeholder="{{ 'app.admin.settings.link_to_about' | translate }}"/>
</div>
</div>
<button name="button" class="btn btn-warning" ng-click="save(linkName)" translate>{{ 'app.shared.buttons.save' }}</button>
</form>
<text-setting name="link_name"
settings="allSettings"
label="app.admin.settings.link_to_about"
fa-icon="fa-link">
</text-setting>
</div>
</div>
</div>

View File

@ -0,0 +1,12 @@
<div class="form-group {{classes}}">
<label for="setting-{{setting.name}}" class="control-label m-r" translate>{{ label }}</label>
<input bs-switch
ng-model="setting.value"
id="setting-{{setting.name}}"
type="checkbox"
class="form-control"
switch-on-text="{{ yesLabel | translate }}"
switch-off-text="{{ noLabel | translate }}"
switch-animate="true"/>
<button name="button" class="btn btn-warning m-l" ng-click="save(setting)" translate>{{ 'app.shared.buttons.save' }}</button>
</div>

View File

@ -1,39 +1,34 @@
<div class="panel panel-default m-t-md">
<div class="panel-heading">
<span class="font-sbold" translate>{{ 'app.admin.settings.title' }}</span>
<span class="font-sbold" translate>{{ 'app.admin.settings.general.title' }}</span>
</div>
<div class="panel-body">
<div class="row m-t-lg">
<div class="col-md-4">
<form role="form" novalidate>
<label for="fablabName" class="control-label m-r" translate>{{ 'app.admin.settings.fablab_title' }}</label>
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-font"></i></div>
<input type="text" id="fablabName" ng-model="fablabName.value" class="form-control" placeholder="{{ 'app.admin.settings.fablab_name' | translate }}"/>
</div>
</div>
<button name="button" class="btn btn-warning" ng-click="save(fablabName)" translate>{{ 'app.shared.buttons.save' }}</button>
</form>
<text-setting name="fablab_name"
settings="allSettings"
label="app.admin.settings.general.fablab_title"
fa-icon="fa-font">
</text-setting>
</div>
<div class="col-md-4 col-md-offset-1">
<form role="form" novalidate>
<h4 class="control-label m-r" translate>{{ 'app.admin.settings.title_concordance' }}</h4>
<h4 class="control-label m-r" translate>{{ 'app.admin.settings.general.title_concordance' }}</h4>
<div class="form-group">
<label for="nameGenreMale">
<input type="radio" name="nameGenre" id="nameGenreMale" ng-model="nameGenre.value" ng-value="'male'" />
{{ 'app.admin.settings.male' | translate }} <span style="font-weight: normal">{{ 'app.admin.settings.eg' | translate }} <cite>{{ 'app.admin.settings.the_team' | translate }} <strong translate>{{ 'app.admin.settings.male_preposition' }}</strong> {{fablabName.value}}</cite></span>
{{ 'app.admin.settings.general.male' | translate }} <span style="font-weight: normal">{{ 'app.admin.settings.general.eg' | translate }} <cite>{{ 'app.admin.settings.general.the_team' | translate }} <strong translate>{{ 'app.admin.settings.general.male_preposition' }}</strong> {{allSettings.fablab_name}}</cite></span>
</label>
<br/>
<label for="nameGenreFemale">
<input type="radio" name="nameGenre" id="nameGenreFemale" ng-model="nameGenre.value" ng-value="'female'" />
{{ 'app.admin.settings.female' | translate }} <span style="font-weight: normal">{{ 'app.admin.settings.eg' | translate }} <cite>{{ 'app.admin.settings.the_team' | translate }} <strong translate>{{ 'app.admin.settings.female_preposition' }}</strong> {{fablabName.value}}</cite></span>
{{ 'app.admin.settings.general.female' | translate }} <span style="font-weight: normal">{{ 'app.admin.settings.general.eg' | translate }} <cite>{{ 'app.admin.settings.general.the_team' | translate }} <strong translate>{{ 'app.admin.settings.general.female_preposition' }}</strong> {{allSettings.fablab_name}}</cite></span>
</label>
<br/>
<label for="nameGenreNeutral">
<input type="radio" name="nameGenre" id="nameGenreNeutral" ng-model="nameGenre.value" ng-value="'neutral'" />
{{ 'app.admin.settings.neutral' | translate }} <span style="font-weight: normal">{{ 'app.admin.settings.eg' | translate }} <cite>{{ 'app.admin.settings.the_team' | translate }} <strong translate>{{ 'app.admin.settings.neutral_preposition' }}</strong> {{fablabName.value}}</cite></span>
{{ 'app.admin.settings.general.neutral' | translate }} <span style="font-weight: normal">{{ 'app.admin.settings.general.eg' | translate }} <cite>{{ 'app.admin.settings.general.the_team' | translate }} <strong translate>{{ 'app.admin.settings.general.neutral_preposition' }}</strong> {{allSettings.fablab_name}}</cite></span>
</label>
</div>
<button name="button" class="btn btn-warning" ng-click="save(nameGenre)" translate>{{ 'app.shared.buttons.save' }}</button>
@ -94,7 +89,7 @@
</div>
<button name="button" class="btn btn-warning" ng-click="save(eventExplicationsAlert)" translate>{{ 'app.shared.buttons.save' }}</button>
</div>
<div class="col-md-3" ng-hide="fablabWithoutSpaces">
<div class="col-md-3" ng-show="modules.spaces">
<h4 translate>{{ 'app.admin.settings.message_of_the_spaces_page' }}</h4>
<div ng-model="spaceExplicationsAlert.value" medium-editor options='{"placeholder": "{{ "app.admin.settings.type_the_message_content" | translate }}",
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
@ -323,27 +318,172 @@
<div class="panel panel-default m-t-lg">
<div class="panel-heading">
<span class="font-sbold" translate>{{ 'app.admin.settings.elements_ordering' }}</span>
<span class="font-sbold" translate>{{ 'app.admin.settings.general.elements_ordering' }}</span>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-4">
<h3 class="m-l" translate>{{ 'app.admin.settings.machines_order' }}</h3>
<div class="form-group m-l">
<label for="machinesSortBy" class="control-label m-r" translate>{{ 'app.admin.settings.display_machines_sorted_by' }}</label>
<select id="machinesSortBy"
class="form-control"
ng-model="machinesSortBy.value">
<option value="default" translate>{{ 'app.admin.settings.sort_by.default' }}</option>
<option value="name" translate>{{ 'app.admin.settings.sort_by.name' }}</option>
<option value="created_at" translate>{{ 'app.admin.settings.sort_by.created_at' }}</option>
<option value="updated_at" translate>{{ 'app.admin.settings.sort_by.updated_at' }}</option>
</select>
<button name="button" class="btn btn-warning m-t" ng-click="save(machinesSortBy)" translate>{{ 'app.shared.buttons.save' }}</button>
</div>
<div class="col-md-4">
</div>
<h3 class="m-l" translate>{{ 'app.admin.settings.general.machines_order' }}</h3>
<select-setting name="machines_sort_by"
settings="allSettings"
classes="m-l"
label="app.admin.settings.general.display_machines_sorted_by"
option-1="['default', 'app.admin.settings.general.sort_by.default']"
option-2="['name', 'app.admin.settings.general.sort_by.name']"
option-3="['created_at', 'app.admin.settings.general.sort_by.created_at']"
option-4="['updated_at', 'app.admin.settings.general.sort_by.updated_at']">
</select-setting>
</div>
</div>
</div>
</div>
<div class="panel panel-default m-t-lg">
<div class="panel-heading">
<span class="font-sbold" translate>{{ 'app.admin.settings.general.help' }}</span>
</div>
<div class="panel-body">
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.general.feature_tour' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.general.feature_tour_info_html' | translate"></p>
<div class="col-md-4">
<select-setting name="feature_tour_display"
settings="allSettings"
classes="m-l"
required="true"
label="app.admin.settings.general.feature_tour_display_mode"
option-1="['once', 'app.admin.settings.general.display_mode.once']"
option-2="['session', 'app.admin.settings.general.display_mode.session']"
option-3="['manual', 'app.admin.settings.general.display_mode.manual']">
</select-setting>
</div>
</div>
</div>
</div>
<div class="panel panel-default m-t-lg">
<div class="panel-heading">
<span class="font-sbold" translate>{{ 'app.admin.settings.general.notifications' }}</span>
</div>
<div class="panel-body">
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.general.email' }}</h3>
<p class="alert alert-warning m-h-md" translate>{{ 'app.admin.settings.general.email_info' }}</p>
<div class="col-md-4">
<text-setting name="email_from"
settings="allSettings"
label="app.admin.settings.general.email_from"
fa-icon="fa-paper-plane"
placeholder="noreply@example.com"
type="email"
required="true">
</text-setting>
</div>
</div>
</div>
</div>
<div class="panel panel-default m-t-md">
<div class="panel-heading">
<span class="font-sbold" translate>{{ 'app.admin.settings.account_creation' }}</span>
</div>
<div class="panel-body">
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.phone' }}</h3>
<p class="alert alert-warning m-h-md" translate>
{{ 'app.admin.settings.phone_required_info' }}
</p>
<div class="col-md-10 col-md-offset-1">
<boolean-setting name="phone_required"
settings="allSettings"
label="app.admin.settings.phone_is_required"
yes-label="app.shared.buttons.yes"
no-label="app.shared.buttons.no">
</boolean-setting>
</div>
</div>
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.captcha' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.captcha_info_html' | translate"></p>
<div class="col-md-6">
<text-setting name="recaptcha_site_key"
settings="allSettings"
label="app.admin.settings.site_key"
fa-icon="fa-info"
placeholder="0000000000000000000000000000000000000000">
</text-setting>
</div>
<div class="col-md-6">
<text-setting name="recaptcha_secret_key"
settings="allSettings"
label="app.admin.settings.secret_key"
fa-icon="fa-key"
placeholder="0000000000000000000000000000000000000000">
</text-setting>
</div>
</div>
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.account_confirmation' }}</h3>
<p class="alert alert-warning m-h-md" translate>
{{ 'app.admin.settings.confirmation_required_info' }}
</p>
<div class="col-md-10 col-md-offset-1">
<boolean-setting name="confirmation_required"
settings="allSettings"
label="app.admin.settings.confirmation_is_required"
yes-label="app.shared.buttons.yes"
no-label="app.shared.buttons.no">
</boolean-setting>
</div>
</div>
</div>
</div>
<div class="panel panel-default m-t-lg">
<div class="panel-heading">
<span class="font-sbold" translate>{{ 'app.admin.settings.modules' }}</span>
</div>
<div class="panel-body">
<p class="alert alert-info"><i class="fa fa-info-circle m-r"></i><span translate>{{ 'app.admin.settings.remember_to_refresh_the_page_for_the_changes_to_take_effect' }}</span> </p>
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.spaces' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.spaces_info_html' | translate"></p>
<boolean-setting name="spaces_module"
settings="allSettings"
label="app.admin.settings.enable_spaces"
classes="m-l"
yes-label="app.shared.buttons.yes"
no-label="app.shared.buttons.no"></boolean-setting>
</div>
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.plans' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.plans_info_html' | translate"></p>
<boolean-setting name="plans_module"
settings="allSettings"
label="app.admin.settings.enable_plans"
classes="m-l"
yes-label="app.shared.buttons.yes"
no-label="app.shared.buttons.no"></boolean-setting>
</div>
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.invoicing' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.invoicing_info_html' | translate"></p>
<boolean-setting name="invoicing_module"
settings="allSettings"
label="app.admin.settings.enable_invoicing"
classes="m-l"
yes-label="app.shared.buttons.yes"
no-label="app.shared.buttons.no"></boolean-setting>
</div>
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.general.wallet' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.general.wallet_info_html' | translate"></p>
<boolean-setting name="wallet_module"
settings="allSettings"
label="app.admin.settings.general.enable_wallet"
classes="m-l"
yes-label="app.shared.buttons.yes"
no-label="app.shared.buttons.no"></boolean-setting>
</div>
</div>
</div>

View File

@ -20,20 +20,11 @@
<button name="button" class="btn btn-warning" ng-click="save(homeBlogpostSetting)" translate>{{ 'app.shared.buttons.save' }}</button>
</div>
<div class="col-md-6">
<h4 translate>{{ 'app.admin.settings.twitter_stream' }}</h4>
<form role="form" class="form-inline" name="twitterForm" novalidate>
<div class="form-group">
<div class="input-group">
<div class="input-group-addon">
<i class="fa fa-twitter"></i>
</div>
<input type="text" ng-model="twitterSetting.value" class="form-control" placeholder="{{ 'app.admin.settings.name_of_the_twitter_account' | translate }}"/>
</div>
</div>
<div class="form-group">
<button name="button" class="btn btn-warning" ng-click="save(twitterSetting)" translate>{{ 'app.shared.buttons.save' }}</button>
</div>
</form>
<text-setting name="twitter_name"
settings="allSettings"
label="app.admin.settings.twitter_stream"
fa-icon="fa-twitter">
</text-setting>
</div>
</div>
<div class="row m-t-lg home-page-style">

View File

@ -26,7 +26,7 @@
<div class="col-md-12">
<uib-tabset justified="true" active="tabs.active">
<uib-tab heading="{{ 'app.admin.settings.general' | translate }}" index="0">
<uib-tab heading="{{ 'app.admin.settings.general.general' | translate }}" index="0" class="general-page-tab">
<ng-include src="'<%= asset_path "admin/settings/general.html" %>'"></ng-include>
</uib-tab>
@ -42,7 +42,7 @@
<ng-include src="'<%= asset_path "admin/settings/privacy.html" %>'"></ng-include>
</uib-tab>
<uib-tab heading="{{ 'app.admin.settings.reservations' | translate }}" index="4">
<uib-tab heading="{{ 'app.admin.settings.reservations' | translate }}" index="4" class="reservations-page-tab">
<ng-include src="'<%= asset_path "admin/settings/reservations.html" %>'"></ng-include>
</uib-tab>
</uib-tabset>

View File

@ -0,0 +1,15 @@
<form class="{{classes}}" name="settingNumberForm">
<label for="setting-{{setting.name}}" class="control-label m-r" translate>{{ label }}</label>
<div class="form-group">
<div class="input-group">
<div class="input-group-addon">
<i class="fa {{faIcon}}"></i>
</div>
<input type="number" class="form-control" id="setting-{{setting.name}}" ng-model="setting.value" min="{{min}}" ng-required="required">
</div>
<span class="help-block text-info text-xs" ng-show="helperText">
<i class="fa fa-lightbulb-o"></i> {{ helperText | translate }}
</span>
</div>
<button name="button" class="btn btn-warning" ng-click="save(setting)" ng-disabled="settingNumberForm.$invalid" translate>{{ 'app.shared.buttons.save' }}</button>
</form>

View File

@ -1,11 +1,11 @@
<div class="panel panel-default m-t-md">
<div class="panel-heading">
<span class="font-sbold" translate>{{ 'app.admin.settings.privacy.title' }}</span>
<span class="font-sbold" translate>{{ 'app.admin.settings.privacy.privacy_policy' }}</span>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-4 col-md-offset-1">
<div class="col-md-6 col-md-offset-1">
<select class="form-control m-b history-select" ng-options="d.id as d.name for d in privacyDraftsHistory" ng-model="privacyPolicy.version" ng-change="handlePolicyRevisionChange()">
<option value="" translate>{{ 'app.admin.settings.privacy.current_policy' }}</option>
</select>
@ -17,7 +17,7 @@
<span class="help-block text-info text-xs"><i class="fa fa-lightbulb-o"></i> {{ 'app.admin.settings.drag_and_drop_to_insert_images' | translate }}</span>
<button name="button" class="btn btn-warning" ng-click="savePrivacyPolicy()" translate>{{ 'app.shared.buttons.save' }}</button>
</div>
<div class="col-md-4 col-md-offset-2">
<div class="col-md-3 col-md-offset-1">
<div ng-model="privacyDpoSetting.value" medium-editor options='{"placeholder": "{{ "app.admin.settings.privacy.input_the_dpo" | translate }}",
"buttons": ["bold", "italic", "anchor", "header1", "header2" ]
}'>
@ -38,22 +38,62 @@
<div class="panel-body">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<label for="is_recurrent" translate>{{ 'app.admin.settings.fab_analytics' }}</label>
<input bs-switch
ng-model="fabAnalytics.value"
id="is_recurrent"
type="checkbox"
class="form-control"
switch-on-text="{{ 'app.shared.buttons.yes' | translate }}"
switch-off-text="{{ 'app.shared.buttons.no' | translate }}"
switch-animate="true"/>
<boolean-setting
name="fab_analytics"
settings="allSettings"
label="app.admin.settings.fab_analytics">
</boolean-setting>
<p>
<span translate>{{ 'app.admin.settings.privacy.about_analytics' }}</span>
<a ng-click="analyticsModal()" class="pointer" translate>{{ 'app.admin.settings.privacy.read_more' }}</a>
</p>
<button name="button" class="btn btn-warning" ng-click="save(fabAnalytics)" translate>{{ 'app.shared.buttons.save' }}</button>
</div>
</div>
</div>
</div>
<div class="panel panel-default m-t-md">
<div class="panel-heading">
<span class="font-sbold" translate>{{ 'app.admin.settings.privacy.statistics' }}</span>
</div>
<div class="panel-body">
<p class="alert alert-info"><i class="fa fa-info-circle m-r"></i><span translate>{{ 'app.admin.settings.remember_to_refresh_the_page_for_the_changes_to_take_effect' }}</span> </p>
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.privacy.google_analytics' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.privacy.tracking_id_info_html' | translate"></p>
<div class="col-md-4 col-md-offset-1">
<text-setting name="tracking_id"
settings="allSettings"
label="app.admin.settings.privacy.tracking_id"
placeholder="UA-000000-2">
</text-setting>
</div>
</div>
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.privacy.facebook' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.privacy.facebook_info_html' | translate"></p>
<div class="col-md-4 col-md-offset-1">
<text-setting name="facebook_app_id"
settings="allSettings"
label="app.admin.settings.privacy.app_id"
placeholder="000000000000000">
</text-setting>
</div>
</div>
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.privacy.twitter' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.privacy.twitter_info_html' | translate"></p>
<div class="col-md-4 col-md-offset-1">
<text-setting name="twitter_analytics"
settings="allSettings"
label="app.admin.settings.privacy.twitter_analytics"
placeholder="@username">
</text-setting>
</div>
</div>
</div>
</div>

View File

@ -21,90 +21,76 @@
<button name="button" class="btn btn-warning m-l" ng-click="save(windowEnd)" translate>{{ 'app.shared.buttons.save' }}</button>
</div>
</div>
<div class="section-separator"></div>
<div class="row">
<h3 class="m-l m-t-lg" translate>{{ 'app.admin.settings.max_visibility' }}</h3>
<form class="col-md-4" name="visibilityYearlyForm">
<label for="yearlySubscribers" class="control-label m-r" translate>{{ 'app.admin.settings.visibility_for_yearly_members' }}</label>
<div class="form-group">
<div class="input-group">
<div class="input-group-addon">
<i class="fa fa-calendar"></i>
</div>
<input type="number" class="form-control" id="yearlySubscribers" ng-model="visibilityYearly.value" min="1" required>
</div>
</div>
<button name="button" class="btn btn-warning" ng-click="save(visibilityYearly)" ng-disabled="visibilityYearlyForm.$invalid" translate>{{ 'app.shared.buttons.save' }}</button>
</form>
<form class="col-md-4 col-md-offset-2" name="visibilityOthersForm">
<label for="others" class="control-label m-r" translate>{{ 'app.admin.settings.visibility_for_other_members' }}</label>
<div class="form-group">
<div class="input-group">
<div class="input-group-addon">
<i class="fa fa-calendar"></i>
</div>
<input type="number" class="form-control" id="others" ng-model="visibilityOthers.value" min="1" required>
</div>
</div>
<button name="button" class="btn btn-warning" ng-click="save(visibilityOthers)" ng-disabled="visibilityOthersForm.$invalid" translate>{{ 'app.shared.buttons.save' }}</button>
</form>
<number-setting name="visibility_yearly"
settings="allSettings"
label="app.admin.settings.visibility_for_yearly_members"
classes="col-md-4"
fa-icon="fa-calendar"
min="1"
required="required">
</number-setting>
<number-setting name="visibility_others"
settings="allSettings"
label="app.admin.settings.visibility_for_other_members"
classes="col-md-4 col-md-offset-2"
fa-icon="fa-calendar"
min="1"
required="required">
</number-setting>
</div>
<div class="section-separator"></div>
<div class="row">
<h3 class="m-l m-t-lg" translate>{{ 'app.admin.settings.ability_for_the_users_to_move_their_reservations' }}</h3>
<div class="form-group m-l">
<label for="enableMove" class="control-label m-r" translate>{{ 'app.admin.settings.reservations_shifting' }}</label>
<input bs-switch
ng-model="enableMove.value"
id="enableMove"
type="checkbox"
class="form-control"
switch-on-text="{{ 'app.admin.settings.enabled' | translate }}"
switch-off-text="{{ 'app.admin.settings.disabled' | translate }}"
switch-animate="true"/>
<button name="button" class="btn btn-warning m-l" ng-click="save(enableMove)" translate>{{ 'app.shared.buttons.save' }}</button>
<div class="col-md-6">
<boolean-setting name="booking_move_enable" settings="allSettings" label="app.admin.settings.reservations_shifting" classes="m-l"></boolean-setting>
</div>
<div class="col-md-6" ng-show="allSettings.booking_move_enable === 'true'">
<number-setting name="booking_move_delay"
settings="allSettings"
label="app.admin.settings.prior_period_hours"
classes="col-md-8"
fa-icon="fa-clock-o"
min="0"
required="allSettings.booking_move_enable === 'true'">
</number-setting>
</div>
</div>
<div class="row" ng-show="enableMove.value">
<form class="col-md-4" name="moveDelayForm">
<label for="moveDelay" class="control-label m-r" translate>{{ 'app.admin.settings.prior_period_hours' }}</label>
<div class="form-group">
<div class="input-group">
<div class="input-group-addon">
<i class="fa fa-clock-o"></i>
</div>
<input type="number" class="form-control" id="moveDelay" ng-model="moveDelay.value" min="0" ng-required="enableMove.value">
</div>
</div>
<button name="button" class="btn btn-warning" ng-click="save(moveDelay)" ng-disabled="moveDelayForm.$invalid" translate>{{ 'app.shared.buttons.save' }}</button>
</form>
</div>
<div class="row">
<h3 class="m-l m-t-lg" translate>{{ 'app.admin.settings.ability_for_the_users_to_cancel_their_reservations' }}</h3>
<div class="form-group m-l">
<label for="enableCancel" class="control-label m-r" translate>{{ 'app.admin.settings.reservations_cancelling' }}</label>
<input bs-switch
ng-model="enableCancel.value"
id="enableCancel"
type="checkbox"
class="form-control"
switch-on-text="{{ 'app.admin.settings.enabled' | translate }}"
switch-off-text="{{ 'app.admin.settings.disabled' | translate }}"
switch-animate="true"/>
<button name="button" class="btn btn-warning m-l" ng-click="save(enableCancel)" translate>{{ 'app.shared.buttons.save' }}</button>
<div class="col-md-6">
<boolean-setting name="booking_cancel_enable" settings="allSettings" label="app.admin.settings.reservations_cancelling" classes="m-l"></boolean-setting>
</div>
<div class="col-md-6" ng-show="allSettings.booking_cancel_enable === 'true'">
<number-setting name="booking_cancel_delay"
settings="allSettings"
label="app.admin.settings.prior_period_hours"
classes="col-md-8"
fa-icon="fa-clock-o"
min="0"
required="allSettings.booking_cancel_enable === 'true'">
</number-setting>
</div>
</div>
<div class="row" ng-show="enableCancel.value">
<form class="col-md-4" name="cancelDelayForm">
<label for="cancelDelay" class="control-label m-r" translate>{{ 'app.admin.settings.prior_period_hours' }}</label>
<div class="form-group">
<div class="input-group">
<div class="input-group-addon">
<i class="fa fa-clock-o"></i>
</div>
<input type="number" class="form-control" id="cancelDelay" ng-model="cancelDelay.value" min="0" ng-required="enableCancel.value">
</div>
</div>
<button name="button" class="btn btn-warning" ng-click="save(cancelDelay)" ng-disabled="cancelDelayForm.$invalid" translate>{{ 'app.shared.buttons.save' }}</button>
</form>
<div class="section-separator"></div>
<div class="row">
<h3 class="m-l m-t-lg" translate>{{ 'app.admin.settings.book_overlapping_slots_info' }}</h3>
<boolean-setting name="book_overlapping_slots" settings="allSettings" label="app.admin.settings.allow_booking" classes="m-l"></boolean-setting>
</div>
<div class="section-separator"></div>
<div class="row">
<h3 class="m-l m-t-lg" translate>{{ 'app.admin.settings.default_slot_duration' }}</h3>
<p class="alert alert-warning m-h-md" translate>{{ 'app.admin.settings.default_slot_duration_info' }}</p>
<number-setting name="slot_duration"
settings="allSettings"
label="app.admin.settings.duration_minutes"
classes="col-md-4"
fa-icon="fa-clock-o"
min="1"
required="required">
</number-setting>
</div>
</div>
</div>
@ -117,35 +103,17 @@
<div class="panel-body">
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.notification_sending_before_the_reservation_occurs' }}</h3>
<div class="form-group m-l">
<label for="enableReminder" class="control-label m-r" translate>{{ 'app.admin.settings.reservations_reminders' }}</label>
<input bs-switch
ng-model="enableReminder.value"
id="enableReminder"
type="checkbox"
class="form-control"
switch-on-text="{{ 'app.admin.settings.enabled' | translate }}"
switch-off-text="{{ 'app.admin.settings.disabled' | translate }}"
switch-animate="true"/>
<button name="button" class="btn btn-warning m-l" ng-click="save(enableReminder)" translate>{{ 'app.shared.buttons.save' }}</button>
</div>
<boolean-setting name="reminder_enable" settings="allSettings" label="app.admin.settings.reservations_reminders" classes="m-l"></boolean-setting>
</div>
<div class="row" ng-show="enableReminder.value">
<form class="col-md-4" name="reminderDelayForm">
<label for="reminderDelay" class="control-label m-r" translate>{{ 'app.admin.settings.prior_period_hours' }}</label>
<div class="form-group">
<div class="input-group">
<div class="input-group-addon">
<i class="fa fa-clock-o"></i>
</div>
<input type="number" class="form-control" id="reminderDelay" ng-model="reminderDelay.value" min="0">
</div>
<span class="help-block text-info text-xs">
<i class="fa fa-lightbulb-o"></i> {{ 'app.admin.settings.default_value_is_24_hours' | translate }}
</span>
</div>
<button name="button" class="btn btn-warning" ng-click="save(reminderDelay)" ng-disabled="reminderDelayForm.$invalid" translate>{{ 'app.shared.buttons.save' }}</button>
</form>
<div class="row" ng-show="allSettings.reminder_enable === 'true'">
<number-setting name="reminder_delay"
settings="allSettings"
label="app.admin.settings.prior_period_hours"
classes="col-md-4"
fa-icon="fa-clock-o"
helper-text="app.admin.settings.default_value_is_24_hours"
min="0">
</number-setting>
</div>
</div>
</div>
@ -153,23 +121,18 @@
<div class="panel panel-default m-t-lg">
<div class="panel-heading">
<span class="font-sbold" translate>{{ 'app.admin.settings.confidentiality' }}</span>
<span class="font-sbold" translate>{{ 'app.admin.settings.display' }}</span>
</div>
<div class="panel-body">
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.display_machine_reservation_user_name' }}</h3>
<div class="form-group m-l">
<label for="displayNameEnable" class="control-label m-r" translate>{{ 'app.admin.settings.display_name' }}</label>
<input bs-switch
ng-model="displayNameEnable.value"
id="displayNameEnable"
type="checkbox"
class="form-control"
switch-on-text="{{ 'app.admin.settings.enabled' | translate }}"
switch-off-text="{{ 'app.admin.settings.disabled' | translate }}"
switch-animate="true"/>
<button name="button" class="btn btn-warning m-l" ng-click="save(displayNameEnable)" translate>{{ 'app.shared.buttons.save' }}</button>
</div>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.display_name_info_html' | translate"></p>
<boolean-setting name="display_name_enable" settings="allSettings" label="app.admin.settings.display_name" classes="m-l"></boolean-setting>
</div>
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.events_in_the_calendar' }}</h3>
<p class="alert alert-warning m-h-md" translate>{{ 'app.admin.settings.events_in_calendar_info' }}</p>
<boolean-setting name="events_in_calendar" settings="allSettings" label="app.admin.settings.show_event" classes="m-l"></boolean-setting>
</div>
</div>
</div>

View File

@ -0,0 +1,35 @@
<form class="{{classes}}" name="settingSelectMultipleForm">
<div class="form-group">
<label for="setting-{{setting.name}}" class="control-label m-r" translate>{{ label }}</label>
<select class="form-control"
id="setting-{{setting.name}}"
ng-model="selection"
ng-required="required"
ng-options="opt for opt in options"
multiple>
</select>
</div>
<div>
<button ng-click="removeItem()" class="btn btn-default"><i class="fa fa-trash"></i></button>
<button ng-click="addItem()" class="btn btn-default"><i class="fa fa-plus"></i></button>
</div>
<button name="button" class="btn btn-warning m-t" ng-click="save(setting)" ng-disabled="settingSelectMultipleForm.$invalid" translate>{{ 'app.shared.buttons.save' }}</button>
</form>
<script type="text/ng-template" id="newSelectOption.html">
<div>
<div class="modal-header">
<h3 class="modal-title" translate>{{ titleNew }}</h3>
</div>
<div class="modal-body">
<p class="alert alert-info" ng-show="descriptionNew" ng-bind-html="descriptionNew | translate"></p>
<form class="row m-md" name="newSelectOptionForm">
<input type="text" class="form-control" ng-model="value" required>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-warning" ng-disabled="newSelectOptionForm.$invalid" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
<button class="btn btn-default" ng-click="dismiss()" translate>{{ 'app.shared.buttons.cancel' }}</button>
</div>
</div>
</script>

View File

@ -0,0 +1,16 @@
<form class="{{classes}}" name="settingSelectForm">
<div class="form-group">
<label for="setting-{{setting.name}}" class="control-label m-r" translate>{{ label }}</label>
<select class="form-control"
id="setting-{{setting.name}}"
ng-model="setting.value"
ng-required="required">
<option ng-if="option1" ng-value="option1[0]" translate>{{ option1[1] }}</option>
<option ng-if="option2" ng-value="option2[0]" translate>{{ option2[1] }}</option>
<option ng-if="option2" ng-value="option3[0]" translate>{{ option3[1] }}</option>
<option ng-if="option4" ng-value="option4[0]" translate>{{ option4[1] }}</option>
<option ng-if="option5" ng-value="option5[0]" translate>{{ option5[1] }}</option>
</select>
</div>
<button name="button" class="btn btn-warning m-t" ng-click="save(setting)" ng-disabled="settingSelectForm.$invalid" translate>{{ 'app.shared.buttons.save' }}</button>
</form>

View File

@ -0,0 +1,20 @@
<form class="{{classes}}" name="settingTextForm">
<label for="setting-{{setting.name}}" class="control-label m-r" translate>{{ label }}</label>
<div ng-class="{'form-group': faIcon}">
<div ng-class="{'input-group': faIcon}">
<div class="input-group-addon" ng-if="faIcon">
<i class="fa {{faIcon}}"></i>
</div>
<input type="{{type}}"
class="form-control"
id="setting-{{setting.name}}"
placeholder="{{placeholder}}"
ng-model="setting.value"
ng-required="required"
ng-minlength="minLength"
ng-maxlength="maxLength"
ng-readonly="readOnly">
</div>
</div>
<button name="button" class="btn btn-warning m-t" ng-click="save(setting)" ng-disabled="settingTextForm.$invalid || readOnly" translate>{{ 'app.shared.buttons.save' }}</button>
</form>

View File

@ -102,7 +102,7 @@
</form>
<uib-tabset justified="true">
<uib-tab ng-repeat="stat in statistics" heading="{{stat.label}}" select="setActiveTab(stat)" ng-if="stat.graph && !(stat.es_type_key == 'subscription' && fablabWithoutPlans)" class="row">
<uib-tab ng-repeat="stat in statistics" heading="{{stat.label}}" select="setActiveTab(stat)" ng-if="stat.graph && !(stat.es_type_key == 'subscription' && modules.plans)" class="row">
<div ng-if="stat.graph.chart_type == 'discreteBarChart'">
<div id="rankingFilters">

View File

@ -18,7 +18,7 @@
<input class="col-md-1 col-sm-1 col-xs-1" type="checkbox" ng-model="m.checked" ng-change="filterAvailabilities(filter)">
</div>
</div>
<div class="m-t" ng-hide="fablabWithoutSpaces">
<div class="m-t" ng-show="modules.spaces">
<div class="row">
<h3 class="col-md-11 col-sm-11 col-xs-11 text-cyan" translate>{{ 'app.public.calendar.spaces' }}</h3>
<input class="col-md-1 col-sm-1 col-xs-1" type="checkbox" ng-model="filter.spaces" ng-change="toggleFilter('spaces', filter)">

View File

@ -15,8 +15,8 @@
<li ui-sref-active="active"><a class="text-black" href="#" ui-sref="app.logged.dashboard.projects" translate>{{ 'app.public.common.my_projects' }}</a></li>
<li ui-sref-active="active"><a class="text-black" href="#" ui-sref="app.logged.dashboard.trainings" translate>{{ 'app.public.common.my_trainings' }}</a></li>
<li ui-sref-active="active"><a class="text-black" href="#" ui-sref="app.logged.dashboard.events" translate>{{ 'app.public.common.my_events' }}</a></li>
<li ui-sref-active="active" ng-hide="fablabWithoutInvoices"><a class="text-black" href="#" ui-sref="app.logged.dashboard.invoices" translate>{{ 'app.public.common.my_invoices' }}</a></li>
<li ng-hide="fablabWithoutWallet" ui-sref-active="active"><a class="text-black" href="#" ui-sref="app.logged.dashboard.wallet" translate>{{ 'app.public.common.my_wallet' }}</a></li>
<li ui-sref-active="active" ng-show="modules.invoicing"><a class="text-black" href="#" ui-sref="app.logged.dashboard.invoices" translate>{{ 'app.public.common.my_invoices' }}</a></li>
<li ng-show="modules.wallet" ui-sref-active="active"><a class="text-black" href="#" ui-sref="app.logged.dashboard.wallet" translate>{{ 'app.public.common.my_wallet' }}</a></li>
</ul>
</section>
</div>

View File

@ -34,7 +34,7 @@
<button class="btn btn-success m-t" ng-click="selectGroup()">Changer mon groupe</button>
</div>
</div>
<div ng-hide="fablabWithoutPlans">
<div ng-show="modules.plans">
<h3 class="text-u-c" translate>{{ 'app.logged.dashboard.settings.subscription' }}</h3>
<div ng-show="user.subscribed_plan">
<uib-alert type="warning">

View File

@ -30,7 +30,7 @@
</div>
<div>
<span class="btn btn-default btn-file"><span class="fileinput-new">{{ 'app.shared.event.choose_a_picture' | translate }} <i class="fa fa-upload fa-fw"></i></span><span class="fileinput-exists" translate>{{ 'app.shared.buttons.change' }}</span>
<input type="file" name="event[event_image_attributes][attachment]"></span>
<input type="file" name="event[event_image_attributes][attachment]" accept="image/jpeg,image/gif,image/png"></span>
<a class="btn btn-danger fileinput-exists" data-dismiss="fileinput"><i class="fa fa-trash-o"></i></a>
</div>
</div>
@ -66,7 +66,7 @@
<i class="glyphicon glyphicon-file fileinput-exists"></i> <span class="fileinput-filename">{{file.attachment}}</span>
</div>
<span class="input-group-addon btn btn-default btn-file"><span class="fileinput-new" translate>{{ 'app.shared.buttons.browse' }}</span>
<span class="fileinput-exists" translate>{{ 'app.shared.buttons.change' }}</span><input type="file" name="event[event_files_attributes][][attachment]"></span>
<span class="fileinput-exists" translate>{{ 'app.shared.buttons.change' }}</span><input type="file" name="event[event_files_attributes][][attachment]" accept="application/pdf"></span>
<a class="input-group-addon btn btn-danger fileinput-exists" data-dismiss="fileinput" ng-click="deleteFile(file)"><i class="fa fa-trash-o"></i></a>
</div>

View File

@ -45,7 +45,7 @@
<input type="file"
ng-model="machine.machine_image"
name="machine[machine_image_attributes][attachment]"
accept="image/*"
accept="image/jpeg,image/gif,image/png"
required
bs-jasny-fileinput>
</span>

View File

@ -19,7 +19,7 @@
<div class="row no-gutter machine-reserve">
<div class="col-sm-12 col-md-12 col-lg-9">
<div ui-calendar="calendarConfig" ng-model="eventSources" calendar="calendar" class="wrapper-lg" ng-show="!plansAreShown"></div>
<ng-include ng-if="!fablabWithoutPlans" src="'<%= asset_path "plans/_plan.html" %>'"></ng-include>
<ng-include ng-if="modules.plans" src="'<%= asset_path "plans/_plan.html" %>'"></ng-include>
</div>

View File

@ -30,7 +30,9 @@
</div>
<div>
<span class="btn btn-default btn-file"><span class="fileinput-new">{{ 'app.shared.project.add_an_illustration' | translate }} <i class="fa fa-upload fa-fw"></i></span><span class="fileinput-exists" translate>{{ 'app.shared.buttons.change' }}</span>
<input type="file" name="project[project_image_attributes][attachment]"></span>
<input type="file"
name="project[project_image_attributes][attachment]"
accept="image/jpeg,image/gif,image/png"></span>
<a class="btn btn-danger fileinput-exists" data-dismiss="fileinput" translate>{{ 'app.shared.buttons.delete' }}</a>
</div>
</div>
@ -51,7 +53,9 @@
<i class="glyphicon glyphicon-file fileinput-exists"></i> <span class="fileinput-filename">{{file.attachment}}</span>
</div>
<span class="input-group-addon btn btn-default btn-file"><span class="fileinput-new" translate>{{ 'app.shared.buttons.browse' }}</span>
<span class="fileinput-exists" translate>{{ 'app.shared.buttons.change' }}</span><input type="file" name="project[project_caos_attributes][][attachment]"></span>
<span class="fileinput-exists" translate>{{ 'app.shared.buttons.change' }}</span>
<input type="file" name="project[project_caos_attributes][][attachment]" accept="{{'.'+allowedExtensions.join(',.')}}">
</span>
<a class="input-group-addon btn btn-danger fileinput-exists" data-dismiss="fileinput" ng-click="deleteFile(file)"><i class="fa fa-trash-o"></i></a>
</div>
</div>
@ -115,7 +119,10 @@
</div>
<div>
<span class="btn btn-default btn-file"><span class="fileinput-new">{{ 'app.shared.buttons.browse' | translate }} <i class="fa fa-upload fa-fw"></i></span><span class="fileinput-exists" translate>{{ 'app.shared.buttons.change' }}</span>
<input type="file" name="project[project_steps_attributes][][project_step_images_attributes][][attachment]"></span>
<input type="file"
name="project[project_steps_attributes][][project_step_images_attributes][][attachment]"
accept="image/jpeg,image/gif,image/png">
</span>
<a class="btn btn-danger fileinput-exists" data-dismiss="fileinput" ng-click="deleteProjectStepImage(step, image)" translate>{{ 'app.shared.buttons.delete' }}</a>
</div>
</div>

View File

@ -52,7 +52,7 @@
<coupon show="isSlotsValid() && (!modePlans || selectedPlan)" coupon="coupon.applied" total="totalNoCoupon" user-id="{{user.id}}"></coupon>
<div ng-hide="fablabWithoutPlans">
<div ng-show="modules.plans">
<div ng-if="isSlotsValid() && !user.subscribed_plan" ng-show="!modePlans">
<p class="font-sbold text-base l-h-2x" translate>{{ 'app.shared.cart.to_benefit_from_attractive_prices' }}</p>
<div><button class="btn btn-warning-full rounded btn-block text-xs" ng-click="showPlans()" translate>{{ 'app.shared.cart.view_our_subscriptions' }}</button></div>

View File

@ -23,7 +23,7 @@
ng-hide="preventField['profile.avatar'] && user.profile.user_avatar.attachment_url && !userForm['user[profile_attributes][user_avatar_attributes]'].$dirty">
<span class="fileinput-new" translate>{{ 'app.shared.user.add_an_avatar' }}</span>
<span class="fileinput-exists" translate>{{ 'app.shared.buttons.change' }}</span>
<input type="file" name="user[profile_attributes][user_avatar_attributes][attachment]">
<input type="file" name="user[profile_attributes][user_avatar_attributes][attachment]" accept="image/jpeg,image/gif,image/png">
</span>
<button class="btn btn-danger fileinput-exists"

View File

@ -42,7 +42,7 @@
ng-minlength="8"/>
</div>
<a href="#" ng-click="openResetPassword($event)" class="text-xs">{{ 'app.public.common.password_forgotten' | translate }}</a>
<span ng-if="userConfirmationNeededToSignIn">
<span ng-if="confirmationRequired">
<br><a href="#" ng-click="openConfirmationNewModal($event)" class="text-xs">{{ 'app.public.common.confirm_my_account' | translate }}</a>
</span>
<div class="alert alert-warning m-t-sm m-b-none text-xs p-sm" ng-show='isCapsLockOn' role="alert">

View File

@ -38,8 +38,8 @@
<li><a ui-sref="app.logged.dashboard.projects" translate>{{ 'app.public.common.my_projects' }}</a></li>
<li><a ui-sref="app.logged.dashboard.trainings" translate>{{ 'app.public.common.my_trainings' }}</a></li>
<li><a ui-sref="app.logged.dashboard.events" translate>{{ 'app.public.common.my_events' }}</a></li>
<li><a ui-sref="app.logged.dashboard.invoices" ng-hide="fablabWithoutInvoices" translate>{{ 'app.public.common.my_invoices' }}</a></li>
<li ng-hide="fablabWithoutWallet"><a ui-sref="app.logged.dashboard.wallet" translate>{{ 'app.public.common.my_wallet' }}</a></li>
<li><a ui-sref="app.logged.dashboard.invoices" ng-show="modules.invoicing" translate>{{ 'app.public.common.my_invoices' }}</a></li>
<li ng-show="modules.wallet"><a ui-sref="app.logged.dashboard.wallet" translate>{{ 'app.public.common.my_wallet' }}</a></li>
<li class="divider" ng-if="isAuthorized(['admin', 'manager'])"></li>
<li><a class="text-black pointer" ng-click="help($event)" ng-if="isAuthorized(['admin', 'manager'])"><i class="fa fa-question-circle"></i> <span translate>{{ 'app.public.common.help' }}</span> </a></li>
<li class="divider"></li>

View File

@ -56,7 +56,7 @@
<i class="fa fa-calendar-o"></i> <span translate>{{ 'app.public.common.my_events' }}</span>
</a>
</li>
<li class="hidden-sm hidden-md hidden-lg" ng-hide="fablabWithoutInvoices" ng-if-end>
<li class="hidden-sm hidden-md hidden-lg" ng-show="modules.invoicing" ng-if-end>
<a ui-sref="app.logged.dashboard.invoices">
<i class="fa fa-file-pdf-o"></i> <span translate>{{ 'app.public.common.my_invoices' }}</span>
</a>

View File

@ -32,7 +32,7 @@
id="space_image"
ng-model="space.space_image"
name="space[space_image_attributes][attachment]"
accept="image/*"
accept="image/jpeg,image/gif,image/png"
required
bs-jasny-fileinput>
</span>
@ -98,8 +98,11 @@
<div class="form-control" data-trigger="fileinput">
<i class="glyphicon glyphicon-file fileinput-exists"></i> <span class="fileinput-filename">{{file.attachment}}</span>
</div>
<span class="input-group-addon btn btn-default btn-file"><span class="fileinput-new" translate>{{ 'app.shared.space.attach_a_file' }}</span>
<span class="fileinput-exists" translate>{{ 'app.shared.buttons.change' }}</span><input type="file" name="space[space_files_attributes][][attachment]" accept=".pdf"></span>
<span class="input-group-addon btn btn-default btn-file">
<span class="fileinput-new" translate>{{ 'app.shared.space.attach_a_file' }}</span>
<span class="fileinput-exists" translate>{{ 'app.shared.buttons.change' }}</span>
<input type="file" name="space[space_files_attributes][][attachment]" accept="application/pdf">
</span>
<a class="input-group-addon btn btn-danger fileinput-exists" data-dismiss="fileinput" ng-click="deleteFile(file)"><i class="fa fa-trash-o"></i></a>
</div>

View File

@ -17,7 +17,7 @@
<div class="row no-gutter training-reserve">
<div class="col-sm-12 col-md-12 col-lg-9">
<div ui-calendar="calendarConfig" ng-model="eventSources" calendar="calendar" class="wrapper-lg" ng-show="!plansAreShown"></div>
<ng-include ng-if="!fablabWithoutPlans" src="'<%= asset_path "plans/_plan.html" %>'"></ng-include>
<ng-include ng-if="modules.plans" src="'<%= asset_path "plans/_plan.html" %>'"></ng-include>
</div>

View File

@ -7,7 +7,7 @@
<uib-alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</uib-alert>
<div class="panel panel-default bg-light m-n">
<form name="stripeForm" stripe:form cart-items="cartItems" on-payment-success="onPaymentSuccess" class="form-horizontal">
<form name="stripeForm" stripe:form cart-items="cartItems" on-payment-success="onPaymentSuccess" stripe-key="{{stripeKey}}" class="form-horizontal">
<div class="panel-body">
<h3 class="m-t-xs" ng-if="walletAmount" ng-bind-html="'app.shared.wallet.you_have_amount_in_wallet' | translate:{ amount: numberFilter(walletAmount, 2), currency: currencySymbol }"></h3>

View File

@ -27,7 +27,7 @@
<div class="row no-gutter training-reserve">
<div class="col-sm-12 col-md-12 col-lg-9">
<div ui-calendar="calendarConfig" ng-model="eventSources" calendar="calendar" class="wrapper-lg" ng-show="!plansAreShown"></div>
<ng-include ng-if="!fablabWithoutPlans" src="'<%= asset_path "plans/_plan.html" %>'"></ng-include>
<ng-include ng-if="modules.plans" src="'<%= asset_path "plans/_plan.html" %>'"></ng-include>
</div>

View File

@ -40,7 +40,7 @@
</div>
<hr/>
<div class="text-right m-t" ng-hide="fablabWithoutInvoices">
<div class="text-right m-t" ng-show="modules.invoicing">
<label for="generate_avoir" translate>{{ 'app.shared.wallet.generate_a_refund_invoice' }}</label>
<div class="inline m-l">
<input bs-switch

View File

@ -2,8 +2,6 @@
# API Controller for resources of type Availability
class API::AvailabilitiesController < API::ApiController
include FablabConfiguration
before_action :authenticate_user!, except: [:public]
before_action :set_availability, only: %i[show update reservations lock]
before_action :define_max_visibility, only: %i[machine trainings spaces]
@ -16,9 +14,9 @@ class API::AvailabilitiesController < API::ApiController
@availabilities = Availability.includes(:machines, :tags, :trainings, :spaces)
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
@availabilities = @availabilities.where.not(available_type: 'event') unless Rails.application.secrets.events_in_calendar
@availabilities = @availabilities.where.not(available_type: 'event') unless Setting.get('events_in_calendar')
@availabilities = @availabilities.where.not(available_type: 'space') if fablab_spaces_deactivated?
@availabilities = @availabilities.where.not(available_type: 'space') unless Setting.get('spaces_module')
end
def public
@ -193,7 +191,7 @@ class API::AvailabilitiesController < API::ApiController
end
def define_max_visibility
@visi_max_year = Setting.find_by(name: 'visibility_yearly').value.to_i.months.since
@visi_max_other = Setting.find_by(name: 'visibility_others').value.to_i.months.since
@visi_max_year = Setting.get('visibility_yearly').to_i.months.since
@visi_max_other = Setting.get('visibility_others').to_i.months.since
end
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
# API Controller for handling special actions on files
class API::FilesController < API::ApiController
before_action :authenticate_user!
# test the mime type of the uploaded file
def mime
authorize :file
content_type = Marcel::MimeType.for Pathname.new(file_params.path)
render json: { type: content_type }
end
private
def file_params
params.require(:attachment)
end
end

View File

@ -7,7 +7,7 @@ class API::MachinesController < API::ApiController
respond_to :json
def index
sort_by = Setting.find_by(name: 'machines_sort_by').value || 'default'
sort_by = Setting.get('machines_sort_by') || 'default'
@machines = if sort_by == 'default'
Machine.includes(:machine_image, :plans)
else

View File

@ -56,7 +56,7 @@ class API::MembersController < API::ApiController
if members_service.update(user_params)
# Update password without logging out
sign_in(@member, bypass: true) unless current_user.id != params[:id].to_i
bypass_sign_in(@member) unless current_user.id != params[:id].to_i
render :show, status: :ok, location: member_path(@member)
else
render json: @member.errors, status: :unprocessable_entity
@ -192,7 +192,7 @@ class API::MembersController < API::ApiController
def complete_tour
authorize @member
if Rails.application.secrets.feature_tour_display == 'session'
if Setting.get('feature_tour_display') == 'session'
render json: { tours: [params[:tour]] }
else
tours = "#{@member.profile.tours} #{params[:tour]}"

View File

@ -3,11 +3,18 @@
# API Controller for resources of type Openlab::Projects
# Openlab::Projects are Projects shared between different instances
class API::OpenlabProjectsController < API::ApiController
PROJECTS = Openlab::Projects.new
before_action :init_openlab
def index
render json: PROJECTS.search(params[:q], page: params[:page], per_page: params[:per_page]).response.body
render json: @projets.search(params[:q], page: params[:page], per_page: params[:per_page]).response.body
rescue StandardError
render json: { errors: ['service unavailable'] }
end
private
def init_openlab
client = Openlab::Client.new(app_secret: Setting.get('openlab_app_secret'))
@projets = Openlab::Projects.new(client)
end
end

View File

@ -10,7 +10,7 @@ class API::PaymentsController < API::ApiController
# was successfully made. After the payment was made, the reservation/subscription will be created
##
def confirm_payment
render(json: { error: 'Online payment is disabled' }, status: :unauthorized) and return if Rails.application.secrets.fablab_without_online_payments
render(json: { error: 'Online payment is disabled' }, status: :unauthorized) and return unless Setting.get('online_payment_module')
amount = nil # will contains the amount and the details of each invoice lines
intent = nil # stripe's payment intent
@ -24,15 +24,17 @@ class API::PaymentsController < API::ApiController
# Create the PaymentIntent
intent = Stripe::PaymentIntent.create(
payment_method: params[:payment_method_id],
amount: amount[:amount],
currency: Rails.application.secrets.stripe_currency,
confirmation_method: 'manual',
confirm: true,
customer: current_user.stp_customer_id
{
payment_method: params[:payment_method_id],
amount: amount[:amount],
currency: Setting.get('stripe_currency'),
confirmation_method: 'manual',
confirm: true,
customer: current_user.stp_customer_id
}, { api_key: Setting.get('stripe_secret_key') }
)
elsif params[:payment_intent_id].present?
intent = Stripe::PaymentIntent.confirm(params[:payment_intent_id])
intent = Stripe::PaymentIntent.confirm(params[:payment_intent_id], api_key: Setting.get('stripe_secret_key'))
end
rescue Stripe::CardError => e
# Display error on client
@ -54,6 +56,16 @@ class API::PaymentsController < API::ApiController
render generate_payment_response(intent, res)
end
def online_payment_status
authorize :payment
key = Setting.get('stripe_secret_key')
render json: { status: false } and return unless key
charges = Stripe::Charge.list({ limit: 1 }, { api_key: key })
render json: { status: charges.data.length.positive? }
end
private
def on_reservation_success(intent, details)
@ -62,7 +74,8 @@ class API::PaymentsController < API::ApiController
.pay_and_save(@reservation, payment_details: details, payment_intent_id: intent.id)
Stripe::PaymentIntent.update(
intent.id,
description: "Invoice reference: #{@reservation.invoice.reference}"
{ description: "Invoice reference: #{@reservation.invoice.reference}" },
{ api_key: Setting.get('stripe_secret_key') }
)
if is_reserve
@ -81,7 +94,8 @@ class API::PaymentsController < API::ApiController
Stripe::PaymentIntent.update(
intent.id,
description: "Invoice reference: #{@subscription.invoices.first.reference}"
{ description: "Invoice reference: #{@subscription.invoices.first.reference}" },
{ api_key: Setting.get('stripe_secret_key') }
)
if is_subscribe

View File

@ -60,10 +60,6 @@ class API::ProjectsController < API::ApiController
render :index
end
def allowed_extensions
render json: ENV['ALLOWED_EXTENSIONS'].split(' '), status: :ok
end
private
def set_project

Some files were not shown because too many files have changed in this diff Show More