mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-07 01:54:16 +01:00
Merge branch 'dev' for release 5.3.1
This commit is contained in:
commit
41f206cc43
70
CHANGELOG.md
70
CHANGELOG.md
@ -1,15 +1,47 @@
|
|||||||
# Changelog Fab-manager
|
# Changelog Fab-manager
|
||||||
|
|
||||||
|
# v5.3.1 2022 January 17
|
||||||
|
|
||||||
|
- Definition of extended prices for spaces is now made in hours (previously in minutes)
|
||||||
|
- Support for JSONPath syntax in OAuth2 SSO fields mapping
|
||||||
|
- Basic support for OAuth2 scopes through an environment variable
|
||||||
|
- Ability to enable debug logs for the SSO authentication process using `SSO_DEBUG=true`
|
||||||
|
- Remove case sensitivity for the SSO account mapping process
|
||||||
|
- Ability to cancel a payement schedule from the interface
|
||||||
|
- Ability to create slots in the past
|
||||||
|
- Ability to disable public account creation
|
||||||
|
- Ability to select "bank transfer" as the payment mean for a payment schedule
|
||||||
|
- When a payment schedule was canceled by the payment gateway, alert the users
|
||||||
|
- When a payment schedule is in error, alert the users
|
||||||
|
- When a payment schedule is in error or canceled, ability to re-enable it with another payment method
|
||||||
|
- Fix card image ratio
|
||||||
|
- Update events heading style
|
||||||
|
- Update some icons
|
||||||
|
- Optimized the load time of the payment schedules list
|
||||||
|
- Optimized multiple DB queries
|
||||||
|
- Updated caniuse db
|
||||||
|
- Fix a bug: do not load Stripe if no keys were defined
|
||||||
|
- Fix a bug: some links redirect to the home page instead of triggering the requested action
|
||||||
|
- Fix a bug: exports to Excel are corrupted (#49)
|
||||||
|
- Fix a bug: if a specialized VAT rate was defined when the VAT was disabled, the resulting VAT rate is wrong
|
||||||
|
- Fix a bug: unable to rebuild the PDF for invoices without subscriptions
|
||||||
|
- Fix a bug: the switch to enable/disable the VAT does not reflect the current state of the VAT
|
||||||
|
- Fix a bug: SSO configuration interface has a misnamed field (Common URL)
|
||||||
|
- Fix a bug: unable to bind Profile.birthday and Profile.gender from an SSO
|
||||||
|
- Fix a security issue: updated follow-redirects to 1.14.7 to fix [CVE-2022-0155](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-0155)
|
||||||
|
- [TODO DEPLOY] `rails db:seed`
|
||||||
|
|
||||||
# v5.3.0 2021 December 29
|
# v5.3.0 2021 December 29
|
||||||
|
|
||||||
- Ability to configure multiple VAT rates, per kind of invoiced item
|
- Ability to configure multiple VAT rates, per kind of invoiced item
|
||||||
- Refactored the extended prices frontend code to allow future customization
|
- Ability to export the collected VAT, by rates, to a CSV file
|
||||||
|
- Refactored the extended prices' frontend code to allow future customization
|
||||||
- Fix a bug: the amount label in not correctly shown in the extended prices modal
|
- Fix a bug: the amount label in not correctly shown in the extended prices modal
|
||||||
- Fix a bug: `extended_prices_in_same_day` apply the extended prices to each days
|
- Fix a bug: `extended_prices_in_same_day` apply the extended prices to each day
|
||||||
|
|
||||||
## v5.2.0 2021 December 23
|
## v5.2.0 2021 December 23
|
||||||
|
|
||||||
- Ability to configure prices for spaces by time slots different than the default hourly rate
|
- Ability to configure prices for spaces, by time slots different from the default hourly rate
|
||||||
- Updated portuguese translation
|
- Updated portuguese translation
|
||||||
- Refactored the ReserveButton component to use the same user's data across all the component
|
- Refactored the ReserveButton component to use the same user's data across all the component
|
||||||
- First optimization the load time of the payment schedules list
|
- First optimization the load time of the payment schedules list
|
||||||
@ -26,21 +58,21 @@
|
|||||||
## v5.1.11 2021 October 22
|
## v5.1.11 2021 October 22
|
||||||
|
|
||||||
- Refactored subscription new/renew/free extend interfaces and API
|
- Refactored subscription new/renew/free extend interfaces and API
|
||||||
- Ability to configure data sources for preventing booking on overlapping slots
|
- Ability to configure the data sources of the booking prevention on overlapping slots
|
||||||
- Updated production documentation
|
- Updated the production documentation
|
||||||
- Updated SSO documentation
|
- Updated the SSO documentation
|
||||||
- Improved stripe subscription process with better error handling
|
- Improved the stripe subscription process with better error handling
|
||||||
- The upgrade script will check and report the ability to access the hub API
|
- The upgrade script will check and report the ability to access the hub API
|
||||||
- Fix a bug: canceled training reservation is not marked as this in admin/edit members/trainings
|
- Fix a bug: canceled training reservation is not marked as this in admin > edit members > trainings
|
||||||
- Fix a bug: users can set their birthdate in the future
|
- Fix a bug: users can set their birthdate in the future
|
||||||
- Fix a bug: the upgrade script won't add environment variables that are already present anymore
|
- Fix a bug: the upgrade script won't add anymore the environment variables that are already present
|
||||||
- Fix a bug: admin cannot take or renew a subscription for a member from member/edit interface
|
- Fix a bug: admin cannot take or renew a subscription for a member from member/edit interface
|
||||||
- Fix a bug: missing translations
|
- Fix a bug: missing translations
|
||||||
- Fix a bug: the upgrade script report an invalid version to upgrade to
|
- Fix a bug: the upgrade script report an invalid version to upgrade to
|
||||||
- Fix a bug: invalid amount provided to the PayZen payment gateway when using a currency with anything else than 2 decimals
|
- Fix a bug: invalid amount provided to the PayZen payment gateway, when using a 0-decimal or a 3-decimal currency
|
||||||
- Fix a bug: incorrect behavior for the setting "email confirmation required"
|
- Fix a bug: incorrect behavior for the setting "email confirmation required"
|
||||||
- Fix a bug: invalid text shown when a member confirms a free cart
|
- Fix a bug: invalid text shown when a member confirms a free cart
|
||||||
- Fix a bug: 3DS confirmation is not asked when an admin is subscribing a user through a payment schedule using PayZen
|
- Fix a bug: 3DS confirmation is not asked when an admin is subscribing a user through a payment schedule, using PayZen
|
||||||
- Updated @rails/webpacker to 5.4.3
|
- Updated @rails/webpacker to 5.4.3
|
||||||
- Updated react-refresh-webpack-plugin to 0.5.1
|
- Updated react-refresh-webpack-plugin to 0.5.1
|
||||||
- Updated react-refresh to 0.10.0
|
- Updated react-refresh to 0.10.0
|
||||||
@ -55,17 +87,17 @@
|
|||||||
|
|
||||||
## v5.1.10 2021 October 04
|
## v5.1.10 2021 October 04
|
||||||
|
|
||||||
- Fix a bug: the image of the about page is not using the image set in backoffice
|
- Fix a bug: the image of the about page is not using the image set in the backoffice
|
||||||
- Fix a bug: updated sassc to 2.4.0 to fix ruby runtime error on some CPU architectures (#270)
|
- Fix a bug: updated sassc to 2.4.0 to fix ruby runtime error on some CPU architectures (#270)
|
||||||
- Fix a security issue: prevent HTML code edition in projects, to prevent XSS vulnerability (#293)
|
- Fix a security issue: prevent HTML code edition in projects, to prevent XSS vulnerability (#293)
|
||||||
- Fix a bug: cover image doesn't display in profile
|
- Fix a bug: cover image doesn't display in profile
|
||||||
- Fix a bug : it redirects to home when we delete a machine record photo
|
- Fix a bug: fab-manager redirects to the home page when we delete a machine photo
|
||||||
|
|
||||||
## v5.1.9 2021 September 21
|
## v5.1.9 2021 September 21
|
||||||
|
|
||||||
- Add a setting for the purchase and use of a prepaid pack is only possible for the user with a valid subscription
|
- Add a setting to restrict the purchase and use of a prepaid pack to users with a valid subscription
|
||||||
- Fix a bug: unable to show plan name in calendar reservations
|
- Fix a bug: unable to view the plans names in the reservation calendar
|
||||||
- Fix a bug: book overlapping slot setting label name
|
- Fix a bug: label name of the book overlapping slot setting
|
||||||
|
|
||||||
## v5.1.8 2021 September 13
|
## v5.1.8 2021 September 13
|
||||||
|
|
||||||
@ -280,7 +312,11 @@
|
|||||||
- [TODO DEPLOY] `rails fablab:maintenance:rebuild_stylesheet`
|
- [TODO DEPLOY] `rails fablab:maintenance:rebuild_stylesheet`
|
||||||
- [TODO DEPLOY] `\curl -sSL https://raw.githubusercontent.com/sleede/fab-manager/master/scripts/rename-adminsys.sh | bash`
|
- [TODO DEPLOY] `\curl -sSL https://raw.githubusercontent.com/sleede/fab-manager/master/scripts/rename-adminsys.sh | bash`
|
||||||
|
|
||||||
## v4.7.13 2020 June 11
|
## v4.7.14 2021 September 30
|
||||||
|
|
||||||
|
- Fix a bug: updated sassc to 2.4.0 to fix ruby runtime error on some CPU architectures
|
||||||
|
|
||||||
|
## v4.7.13 2021 June 11
|
||||||
|
|
||||||
- Fix a bug: unable to process stripe payments with 3DS authentication
|
- Fix a bug: unable to process stripe payments with 3DS authentication
|
||||||
|
|
||||||
|
3
Gemfile
3
Gemfile
@ -14,6 +14,7 @@ gem 'webpacker', '~> 5.x'
|
|||||||
gem 'jbuilder', '~> 2.5'
|
gem 'jbuilder', '~> 2.5'
|
||||||
gem 'jbuilder_cache_multi'
|
gem 'jbuilder_cache_multi'
|
||||||
gem 'json', '>= 2.3.0'
|
gem 'json', '>= 2.3.0'
|
||||||
|
gem 'jsonpath'
|
||||||
|
|
||||||
gem 'forgery'
|
gem 'forgery'
|
||||||
gem 'responders', '~> 2.0'
|
gem 'responders', '~> 2.0'
|
||||||
@ -29,6 +30,7 @@ group :development do
|
|||||||
# Access an IRB console on exception pages or by using <%= console %> in views
|
# Access an IRB console on exception pages or by using <%= console %> in views
|
||||||
gem 'active_record_query_trace'
|
gem 'active_record_query_trace'
|
||||||
gem 'awesome_print'
|
gem 'awesome_print'
|
||||||
|
gem 'bullet'
|
||||||
gem 'coveralls_reborn', '~> 0.18.0', require: false
|
gem 'coveralls_reborn', '~> 0.18.0', require: false
|
||||||
gem 'foreman'
|
gem 'foreman'
|
||||||
gem 'web-console', '>= 3.3.0'
|
gem 'web-console', '>= 3.3.0'
|
||||||
@ -64,6 +66,7 @@ gem 'pg_search'
|
|||||||
# authentication
|
# authentication
|
||||||
gem 'devise', '>= 4.6.0'
|
gem 'devise', '>= 4.6.0'
|
||||||
|
|
||||||
|
|
||||||
gem 'omniauth', '~> 1.9.0'
|
gem 'omniauth', '~> 1.9.0'
|
||||||
gem 'omniauth-oauth2'
|
gem 'omniauth-oauth2'
|
||||||
gem 'omniauth-rails_csrf_protection', '~> 0.1'
|
gem 'omniauth-rails_csrf_protection', '~> 0.1'
|
||||||
|
10
Gemfile.lock
10
Gemfile.lock
@ -67,6 +67,9 @@ GEM
|
|||||||
bootsnap (1.4.6)
|
bootsnap (1.4.6)
|
||||||
msgpack (~> 1.0)
|
msgpack (~> 1.0)
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
|
bullet (7.0.0)
|
||||||
|
activesupport (>= 3.0.0)
|
||||||
|
uniform_notifier (~> 1.11)
|
||||||
camertron-eprun (1.1.1)
|
camertron-eprun (1.1.1)
|
||||||
carrierwave (2.1.1)
|
carrierwave (2.1.1)
|
||||||
activemodel (>= 5.0.0)
|
activemodel (>= 5.0.0)
|
||||||
@ -172,6 +175,8 @@ GEM
|
|||||||
jbuilder_cache_multi (0.1.0)
|
jbuilder_cache_multi (0.1.0)
|
||||||
jbuilder (>= 1.5.0, < 3)
|
jbuilder (>= 1.5.0, < 3)
|
||||||
json (2.3.1)
|
json (2.3.1)
|
||||||
|
jsonpath (1.1.0)
|
||||||
|
multi_json
|
||||||
jwt (2.2.1)
|
jwt (2.2.1)
|
||||||
kaminari (1.2.1)
|
kaminari (1.2.1)
|
||||||
activesupport (>= 4.1.0)
|
activesupport (>= 4.1.0)
|
||||||
@ -394,6 +399,7 @@ GEM
|
|||||||
tzinfo-data (1.2020.4)
|
tzinfo-data (1.2020.4)
|
||||||
tzinfo (>= 1.0.0)
|
tzinfo (>= 1.0.0)
|
||||||
unicode-display_width (1.4.1)
|
unicode-display_width (1.4.1)
|
||||||
|
uniform_notifier (1.14.2)
|
||||||
vcr (6.0.0)
|
vcr (6.0.0)
|
||||||
virtus (1.0.5)
|
virtus (1.0.5)
|
||||||
axiom-types (~> 0.1)
|
axiom-types (~> 0.1)
|
||||||
@ -431,6 +437,7 @@ DEPENDENCIES
|
|||||||
apipie-rails
|
apipie-rails
|
||||||
awesome_print
|
awesome_print
|
||||||
bootsnap
|
bootsnap
|
||||||
|
bullet
|
||||||
carrierwave
|
carrierwave
|
||||||
caxlsx
|
caxlsx
|
||||||
caxlsx_rails
|
caxlsx_rails
|
||||||
@ -451,6 +458,7 @@ DEPENDENCIES
|
|||||||
jbuilder (~> 2.5)
|
jbuilder (~> 2.5)
|
||||||
jbuilder_cache_multi
|
jbuilder_cache_multi
|
||||||
json (>= 2.3.0)
|
json (>= 2.3.0)
|
||||||
|
jsonpath
|
||||||
kaminari
|
kaminari
|
||||||
listen (~> 3.0.5)
|
listen (~> 3.0.5)
|
||||||
message_format
|
message_format
|
||||||
@ -498,4 +506,4 @@ DEPENDENCIES
|
|||||||
webpacker (~> 5.x)
|
webpacker (~> 5.x)
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.1.4
|
2.2.19
|
||||||
|
@ -19,7 +19,7 @@ class API::MembersController < API::ApiController
|
|||||||
|
|
||||||
def last_subscribed
|
def last_subscribed
|
||||||
@query = User.active.with_role(:member)
|
@query = User.active.with_role(:member)
|
||||||
.includes(profile: [:user_avatar])
|
.includes(:statistic_profile, profile: [:user_avatar])
|
||||||
.where('is_allow_contact = true AND confirmed_at IS NOT NULL')
|
.where('is_allow_contact = true AND confirmed_at IS NOT NULL')
|
||||||
.order('created_at desc')
|
.order('created_at desc')
|
||||||
.limit(params[:last])
|
.limit(params[:last])
|
||||||
|
@ -6,12 +6,15 @@ class API::NotificationsController < API::ApiController
|
|||||||
include NotifyWith::NotificationsApi
|
include NotifyWith::NotificationsApi
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
|
|
||||||
|
# notifications can have anything attached, so we won't eager load the whole database
|
||||||
|
around_action :skip_bullet, if: -> { defined?(Bullet) }
|
||||||
|
|
||||||
# Number of notifications added to the page when the user clicks on 'load next notifications'
|
# Number of notifications added to the page when the user clicks on 'load next notifications'
|
||||||
NOTIFICATIONS_PER_PAGE = 15
|
NOTIFICATIONS_PER_PAGE = 15
|
||||||
|
|
||||||
def index
|
def index
|
||||||
loop do
|
loop do
|
||||||
@notifications = current_user.notifications.page(params[:page]).per(NOTIFICATIONS_PER_PAGE).order('created_at DESC')
|
@notifications = current_user.notifications.includes(:attached_object).page(params[:page]).per(NOTIFICATIONS_PER_PAGE).order('created_at DESC')
|
||||||
# we delete obsolete notifications on first access
|
# we delete obsolete notifications on first access
|
||||||
break unless delete_obsoletes(@notifications)
|
break unless delete_obsoletes(@notifications)
|
||||||
end
|
end
|
||||||
@ -24,7 +27,7 @@ class API::NotificationsController < API::ApiController
|
|||||||
|
|
||||||
def last_unread
|
def last_unread
|
||||||
loop do
|
loop do
|
||||||
@notifications = current_user.notifications.where(is_read: false).limit(3).order('created_at DESC')
|
@notifications = current_user.notifications.includes(:attached_object).where(is_read: false).limit(3).order('created_at DESC')
|
||||||
# we delete obsolete notifications on first access
|
# we delete obsolete notifications on first access
|
||||||
break unless delete_obsoletes(@notifications)
|
break unless delete_obsoletes(@notifications)
|
||||||
end
|
end
|
||||||
|
@ -3,9 +3,10 @@
|
|||||||
# API Controller for resources of PaymentSchedule
|
# API Controller for resources of PaymentSchedule
|
||||||
class API::PaymentSchedulesController < API::ApiController
|
class API::PaymentSchedulesController < API::ApiController
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
before_action :set_payment_schedule, only: %i[download cancel]
|
before_action :set_payment_schedule, only: %i[download cancel update]
|
||||||
before_action :set_payment_schedule_item, only: %i[cash_check refresh_item pay_item]
|
before_action :set_payment_schedule_item, only: %i[show_item cash_check confirm_transfer refresh_item pay_item]
|
||||||
|
|
||||||
|
# retrieve all payment schedules for the current user, paginated
|
||||||
def index
|
def index
|
||||||
@payment_schedules = PaymentSchedule.where('invoicing_profile_id = ?', current_user.invoicing_profile.id)
|
@payment_schedules = PaymentSchedule.where('invoicing_profile_id = ?', current_user.invoicing_profile.id)
|
||||||
.includes(:invoicing_profile, :payment_schedule_items, :payment_schedule_objects)
|
.includes(:invoicing_profile, :payment_schedule_items, :payment_schedule_objects)
|
||||||
@ -15,6 +16,7 @@ class API::PaymentSchedulesController < API::ApiController
|
|||||||
.per(params[:size])
|
.per(params[:size])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# retrieve all payment schedules for all users. Filtering is supported
|
||||||
def list
|
def list
|
||||||
authorize PaymentSchedule
|
authorize PaymentSchedule
|
||||||
|
|
||||||
@ -44,6 +46,15 @@ class API::PaymentSchedulesController < API::ApiController
|
|||||||
render json: attrs, status: :ok
|
render json: attrs, status: :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def confirm_transfer
|
||||||
|
authorize @payment_schedule_item.payment_schedule
|
||||||
|
PaymentScheduleService.new.generate_invoice(@payment_schedule_item, payment_method: 'transfer')
|
||||||
|
attrs = { state: 'paid', payment_method: 'transfer' }
|
||||||
|
@payment_schedule_item.update_attributes(attrs)
|
||||||
|
|
||||||
|
render json: attrs, status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
def refresh_item
|
def refresh_item
|
||||||
authorize @payment_schedule_item.payment_schedule
|
authorize @payment_schedule_item.payment_schedule
|
||||||
PaymentScheduleItemWorker.new.perform(@payment_schedule_item.id)
|
PaymentScheduleItemWorker.new.perform(@payment_schedule_item.id)
|
||||||
@ -62,6 +73,11 @@ class API::PaymentSchedulesController < API::ApiController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def show_item
|
||||||
|
authorize @payment_schedule_item.payment_schedule
|
||||||
|
render json: @payment_schedule_item, status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
def cancel
|
def cancel
|
||||||
authorize @payment_schedule
|
authorize @payment_schedule
|
||||||
|
|
||||||
@ -69,6 +85,17 @@ class API::PaymentSchedulesController < API::ApiController
|
|||||||
render json: { canceled_at: canceled_at }, status: :ok
|
render json: { canceled_at: canceled_at }, status: :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
## Only the update of the payment method is allowed
|
||||||
|
def update
|
||||||
|
authorize PaymentSchedule
|
||||||
|
|
||||||
|
if PaymentScheduleService.new.update_payment_mean(@payment_schedule, update_params)
|
||||||
|
render :show, status: :ok, location: @payment_schedule
|
||||||
|
else
|
||||||
|
render json: @payment_schedule.errors, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_payment_schedule
|
def set_payment_schedule
|
||||||
@ -78,4 +105,8 @@ class API::PaymentSchedulesController < API::ApiController
|
|||||||
def set_payment_schedule_item
|
def set_payment_schedule_item
|
||||||
@payment_schedule_item = PaymentScheduleItem.find(params[:id])
|
@payment_schedule_item = PaymentScheduleItem.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_params
|
||||||
|
params.require(:payment_schedule).permit(:payment_method)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -80,4 +80,13 @@ class ApplicationController < ActionController::Base
|
|||||||
def authenticate_user!
|
def authenticate_user!
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# N+1 query detection (https://github.com/flyerhzm/bullet)
|
||||||
|
def skip_bullet
|
||||||
|
previous_value = Bullet.enable?
|
||||||
|
Bullet.enable = false
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
Bullet.enable = previous_value
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -4,6 +4,11 @@
|
|||||||
class RegistrationsController < Devise::RegistrationsController
|
class RegistrationsController < Devise::RegistrationsController
|
||||||
# POST /users.json
|
# POST /users.json
|
||||||
def create
|
def create
|
||||||
|
# Is public registration allowed?
|
||||||
|
unless Setting.get('public_registrations')
|
||||||
|
render json: { errors: { signup: [t('errors.messages.registration_disabled')] } }, status: :forbidden and return
|
||||||
|
end
|
||||||
|
|
||||||
# first check the recaptcha
|
# first check the recaptcha
|
||||||
check = RecaptchaService.verify(params[:user][:recaptcha])
|
check = RecaptchaService.verify(params[:user][:recaptcha])
|
||||||
render json: check['error-codes'], status: :unprocessable_entity and return unless check['success']
|
render json: check['error-codes'], status: :unprocessable_entity and return unless check['success']
|
||||||
|
@ -1,16 +1,25 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Handle authentication actions via OmniAuth (used by SSO providers)
|
||||||
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||||
|
require 'sso_logger'
|
||||||
|
logger = SsoLogger.new
|
||||||
|
|
||||||
active_provider = AuthProvider.active
|
active_provider = AuthProvider.active
|
||||||
define_method active_provider.strategy_name do
|
define_method active_provider.strategy_name do
|
||||||
|
logger.info "[Users::OmniauthCallbacksController##{active_provider.strategy_name}] initiated"
|
||||||
if request.env['omniauth.params'].blank?
|
if request.env['omniauth.params'].blank?
|
||||||
|
logger.debug 'the user has not provided any authentication token'
|
||||||
@user = User.from_omniauth(request.env['omniauth.auth'])
|
@user = User.from_omniauth(request.env['omniauth.auth'])
|
||||||
|
|
||||||
# Here we create the new user or update the existing one with values retrieved from the SSO.
|
# Here we create the new user or update the existing one with values retrieved from the SSO.
|
||||||
|
|
||||||
if @user.id.nil? # => new user (ie. not updating existing)
|
if @user.id.nil? # => new user (ie. not updating existing)
|
||||||
|
logger.debug 'trying to create a new user'
|
||||||
# If the username is mapped, we just check its uniqueness as it would break the postgresql
|
# If the username is mapped, we just check its uniqueness as it would break the postgresql
|
||||||
# unique constraint otherwise. If the name is not unique, another unique is generated
|
# unique constraint otherwise. If the name is not unique, another unique is generated
|
||||||
if active_provider.sso_fields.include?('user.username')
|
if active_provider.sso_fields.include?('user.username')
|
||||||
|
logger.debug 'the username was already in use, generating a new one'
|
||||||
@user.username = generate_unique_username(@user.username)
|
@user.username = generate_unique_username(@user.username)
|
||||||
end
|
end
|
||||||
# If the email is mapped, we check its uniqueness. If the email is already in use, we mark it as duplicate with an
|
# If the email is mapped, we check its uniqueness. If the email is already in use, we mark it as duplicate with an
|
||||||
@ -18,17 +27,21 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||||||
# - if it is the same user, his email will be filled from the SSO when he merge his accounts
|
# - if it is the same user, his email will be filled from the SSO when he merge his accounts
|
||||||
# - if it is not the same user, this will prevent the raise of PG::UniqueViolation
|
# - if it is not the same user, this will prevent the raise of PG::UniqueViolation
|
||||||
if active_provider.sso_fields.include?('user.email') && email_exists?(@user.email)
|
if active_provider.sso_fields.include?('user.email') && email_exists?(@user.email)
|
||||||
|
logger.debug 'the email was already in use, marking it as duplicate'
|
||||||
old_mail = @user.email
|
old_mail = @user.email
|
||||||
@user.email = "<#{old_mail}>#{Devise.friendly_token}-duplicate"
|
@user.email = "<#{old_mail}>#{Devise.friendly_token}-duplicate"
|
||||||
flash[:alert] = t('omniauth.email_already_linked_to_another_account_please_input_your_authentication_code', OLD_MAIL: old_mail)
|
flash[:alert] = t('omniauth.email_already_linked_to_another_account_please_input_your_authentication_code', OLD_MAIL: old_mail)
|
||||||
end
|
end
|
||||||
else # => update of an existing user
|
else # => update of an existing user
|
||||||
|
logger.debug "an existing user was found (id=#{@user.id})"
|
||||||
if username_exists?(@user.username, @user.id)
|
if username_exists?(@user.username, @user.id)
|
||||||
|
logger.debug 'the username was already in use, alerting user'
|
||||||
flash[:alert] = t('omniauth.your_username_is_already_linked_to_another_account_unable_to_update_it', USERNAME: @user.username)
|
flash[:alert] = t('omniauth.your_username_is_already_linked_to_another_account_unable_to_update_it', USERNAME: @user.username)
|
||||||
@user.username = User.find(@user.id).username
|
@user.username = User.find(@user.id).username
|
||||||
end
|
end
|
||||||
|
|
||||||
if email_exists?(@user.email, @user.id)
|
if email_exists?(@user.email, @user.id)
|
||||||
|
logger.debug 'the email was already in use, alerting user'
|
||||||
flash[:alert] = t('omniauth.your_email_address_is_already_linked_to_another_account_unable_to_update_it', EMAIL: @user.email)
|
flash[:alert] = t('omniauth.your_email_address_is_already_linked_to_another_account_unable_to_update_it', EMAIL: @user.email)
|
||||||
@user.email = User.find(@user.id).email
|
@user.email = User.find(@user.id).email
|
||||||
end
|
end
|
||||||
@ -36,19 +49,32 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||||||
|
|
||||||
# We BYPASS THE VALIDATION because, in case of a new user, we want to save him anyway, we'll ask him later to complete his profile (on first login).
|
# We BYPASS THE VALIDATION because, in case of a new user, we want to save him anyway, we'll ask him later to complete his profile (on first login).
|
||||||
# In case of an existing user, we trust the SSO validation as we want the SSO to have authority on users management and policy.
|
# In case of an existing user, we trust the SSO validation as we want the SSO to have authority on users management and policy.
|
||||||
@user.save(validate: false)
|
logger.debug 'saving the user'
|
||||||
|
unless @user.save(validate: false)
|
||||||
|
logger.error "unable to save the user, an error occurred : #{@user.errors.full_messages.join(', ')}"
|
||||||
|
end
|
||||||
|
|
||||||
|
logger.debug 'signing-in the user and redirecting'
|
||||||
sign_in_and_redirect @user, event: :authentication # this will throw if @user is not activated
|
sign_in_and_redirect @user, event: :authentication # this will throw if @user is not activated
|
||||||
else
|
else
|
||||||
|
logger.debug 'the user has provided an authentication token'
|
||||||
@user = User.find_by(auth_token: request.env['omniauth.params']['auth_token'])
|
@user = User.find_by(auth_token: request.env['omniauth.params']['auth_token'])
|
||||||
|
|
||||||
# Here the user already exists in the database and request to be linked with the SSO
|
# Here the user already exists in the database and request to be linked with the SSO
|
||||||
# so let's update its sso attributes and log him on
|
# so let's update its sso attributes and log him on
|
||||||
|
logger.debug "found user id=#{@user.id}"
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
logger.debug 'linking with the omniauth provider'
|
||||||
@user.link_with_omniauth_provider(request.env['omniauth.auth'])
|
@user.link_with_omniauth_provider(request.env['omniauth.auth'])
|
||||||
|
logger.debug 'signing-in the user and redirecting'
|
||||||
sign_in_and_redirect @user, event: :authentication
|
sign_in_and_redirect @user, event: :authentication
|
||||||
rescue DuplicateIndexError
|
rescue DuplicateIndexError
|
||||||
|
logger.error 'user already linked'
|
||||||
redirect_to root_url, alert: t('omniauth.this_account_is_already_linked_to_an_user_of_the_platform', NAME: active_provider.name)
|
redirect_to root_url, alert: t('omniauth.this_account_is_already_linked_to_an_user_of_the_platform', NAME: active_provider.name)
|
||||||
|
rescue StandardError => e
|
||||||
|
logger.unknown "an expected error occurred: #{e}"
|
||||||
|
raise e
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -58,17 +84,17 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||||||
|
|
||||||
def username_exists?(username, exclude_id = nil)
|
def username_exists?(username, exclude_id = nil)
|
||||||
if exclude_id.nil?
|
if exclude_id.nil?
|
||||||
User.where(username: username).size.positive?
|
User.where('lower(username) = ?', username&.downcase).size.positive?
|
||||||
else
|
else
|
||||||
User.where(username: username).where.not(id: exclude_id).size.positive?
|
User.where('lower(username) = ?', username&.downcase).where.not(id: exclude_id).size.positive?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def email_exists?(email, exclude_id = nil)
|
def email_exists?(email, exclude_id = nil)
|
||||||
if exclude_id.nil?
|
if exclude_id.nil?
|
||||||
User.where(email: email).size.positive?
|
User.where('lower(email) = ?', email&.downcase).size.positive?
|
||||||
else
|
else
|
||||||
User.where(email: email).where.not(id: exclude_id).size.positive?
|
User.where('lower(email) = ?', email&.downcase).where.not(id: exclude_id).size.positive?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2,5 +2,8 @@
|
|||||||
|
|
||||||
# Raised when an an error occurred with the PayZen payment gateway
|
# Raised when an an error occurred with the PayZen payment gateway
|
||||||
class PayzenError < PaymentGatewayError
|
class PayzenError < PaymentGatewayError
|
||||||
|
def details
|
||||||
|
JSON.parse(message.gsub('=>', ':').gsub('nil', 'null'))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
CancelScheduleResponse,
|
CancelScheduleResponse,
|
||||||
CashCheckResponse, PayItemResponse,
|
CashCheckResponse, PayItemResponse,
|
||||||
PaymentSchedule,
|
PaymentSchedule,
|
||||||
PaymentScheduleIndexRequest, RefreshItemResponse
|
PaymentScheduleIndexRequest, PaymentScheduleItem, RefreshItemResponse
|
||||||
} from '../models/payment-schedule';
|
} from '../models/payment-schedule';
|
||||||
|
|
||||||
export default class PaymentScheduleAPI {
|
export default class PaymentScheduleAPI {
|
||||||
@ -23,6 +23,16 @@ export default class PaymentScheduleAPI {
|
|||||||
return res?.data;
|
return res?.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async confirmTransfer (paymentScheduleItemId: number): Promise<CashCheckResponse> {
|
||||||
|
const res: AxiosResponse = await apiClient.post(`/api/payment_schedules/items/${paymentScheduleItemId}/confirm_transfer`);
|
||||||
|
return res?.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getItem (paymentScheduleItemId: number): Promise<PaymentScheduleItem> {
|
||||||
|
const res: AxiosResponse = await apiClient.get(`/api/payment_schedules/items/${paymentScheduleItemId}`);
|
||||||
|
return res?.data;
|
||||||
|
}
|
||||||
|
|
||||||
static async refreshItem (paymentScheduleItemId: number): Promise<RefreshItemResponse> {
|
static async refreshItem (paymentScheduleItemId: number): Promise<RefreshItemResponse> {
|
||||||
const res: AxiosResponse = await apiClient.post(`/api/payment_schedules/items/${paymentScheduleItemId}/refresh_item`);
|
const res: AxiosResponse = await apiClient.post(`/api/payment_schedules/items/${paymentScheduleItemId}/refresh_item`);
|
||||||
return res?.data;
|
return res?.data;
|
||||||
@ -37,4 +47,9 @@ export default class PaymentScheduleAPI {
|
|||||||
const res: AxiosResponse = await apiClient.put(`/api/payment_schedules/${paymentScheduleId}/cancel`);
|
const res: AxiosResponse = await apiClient.put(`/api/payment_schedules/${paymentScheduleId}/cancel`);
|
||||||
return res?.data;
|
return res?.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async update (paymentSchedule: PaymentSchedule): Promise<PaymentSchedule> {
|
||||||
|
const res:AxiosResponse<PaymentSchedule> = await apiClient.patch(`/api/payment_schedules/${paymentSchedule.id}`, paymentSchedule);
|
||||||
|
return res?.data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
interface HtmlTranslateProps {
|
interface HtmlTranslateProps {
|
||||||
trKey: string,
|
trKey: string,
|
||||||
options?: Record<string, string>
|
options?: Record<string, string|number>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { Suspense } from 'react';
|
import React, { Suspense } from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component is a wrapper that display a loader while the children components have their rendering suspended
|
* This component is a wrapper that display a loader while the children components have their rendering suspended.
|
||||||
*/
|
*/
|
||||||
export const Loader: React.FC = ({ children }) => {
|
export const Loader: React.FC = ({ children }) => {
|
||||||
const loading = (
|
const loading = (
|
||||||
|
@ -0,0 +1,413 @@
|
|||||||
|
import {
|
||||||
|
PaymentMethod,
|
||||||
|
PaymentSchedule,
|
||||||
|
PaymentScheduleItem,
|
||||||
|
PaymentScheduleItemState
|
||||||
|
} from '../../models/payment-schedule';
|
||||||
|
import React, { ReactElement, useState } from 'react';
|
||||||
|
import { FabButton } from '../base/fab-button';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { User, UserRole } from '../../models/user';
|
||||||
|
import PaymentScheduleAPI from '../../api/payment-schedule';
|
||||||
|
import { FabModal } from '../base/fab-modal';
|
||||||
|
import FormatLib from '../../lib/format';
|
||||||
|
import { StripeConfirmModal } from '../payment/stripe/stripe-confirm-modal';
|
||||||
|
import { UpdateCardModal } from '../payment/update-card-modal';
|
||||||
|
import { UpdatePaymentMeanModal } from './update-payment-mean-modal';
|
||||||
|
|
||||||
|
// we want to display some buttons only once. This is the types of buttons it applies to.
|
||||||
|
export enum TypeOnce {
|
||||||
|
CardUpdate = 'card-update',
|
||||||
|
SubscriptionCancel = 'subscription-cancel',
|
||||||
|
UpdatePaymentMean = 'update-payment-mean'
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PaymentScheduleItemActionsProps {
|
||||||
|
paymentScheduleItem: PaymentScheduleItem,
|
||||||
|
paymentSchedule: PaymentSchedule,
|
||||||
|
onError: (message: string) => void,
|
||||||
|
onSuccess: () => void,
|
||||||
|
onCardUpdateSuccess: () => void
|
||||||
|
operator: User,
|
||||||
|
displayOnceMap: Map<TypeOnce, Map<number, number>>,
|
||||||
|
show: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component is responsible for rendering the actions buttons for a payment schedule item.
|
||||||
|
*/
|
||||||
|
export const PaymentScheduleItemActions: React.FC<PaymentScheduleItemActionsProps> = ({ paymentScheduleItem, paymentSchedule, onError, onSuccess, onCardUpdateSuccess, displayOnceMap, operator, show }) => {
|
||||||
|
const { t } = useTranslation('shared');
|
||||||
|
|
||||||
|
// is open, the modal dialog to cancel the associated subscription?
|
||||||
|
const [showCancelSubscription, setShowCancelSubscription] = useState<boolean>(false);
|
||||||
|
// is open, the modal dialog to confirm the cashing of a check?
|
||||||
|
const [showConfirmCashing, setShowConfirmCashing] = useState<boolean>(false);
|
||||||
|
// is open, the modal dialog to confirm a back transfer?
|
||||||
|
const [showConfirmTransfer, setShowConfirmTransfer] = useState<boolean>(false);
|
||||||
|
// is open, the modal dialog the resolve a pending card payment?
|
||||||
|
const [showResolveAction, setShowResolveAction] = useState<boolean>(false);
|
||||||
|
// is open, the modal dialog to update the card details
|
||||||
|
const [showUpdateCard, setShowUpdateCard] = useState<boolean>(false);
|
||||||
|
// is open, the modal dialog to update the payment mean
|
||||||
|
const [showUpdatePaymentMean, setShowUpdatePaymentMean] = useState<boolean>(false);
|
||||||
|
// the user cannot confirm the action modal (3D secure), unless he has resolved the pending action
|
||||||
|
const [isConfirmActionDisabled, setConfirmActionDisabled] = useState<boolean>(true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current operator has administrative rights or is a normal member
|
||||||
|
*/
|
||||||
|
const isPrivileged = (): boolean => {
|
||||||
|
return (operator.role === UserRole.Admin || operator.role === UserRole.Manager);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a button to download a PDF invoice file
|
||||||
|
*/
|
||||||
|
const downloadInvoiceButton = (id: number): JSX.Element => {
|
||||||
|
const link = `api/invoices/${id}/download`;
|
||||||
|
return (
|
||||||
|
<a href={link} target="_blank" className="download-button" rel="noreferrer">
|
||||||
|
<i className="fas fa-download" />
|
||||||
|
{t('app.shared.payment_schedule_item_actions.download')}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a button to cancel the given subscription, if the user is privileged enough
|
||||||
|
*/
|
||||||
|
const cancelSubscriptionButton = (): ReactElement => {
|
||||||
|
const displayOnceStatus = displayOnceMap.get(TypeOnce.SubscriptionCancel).get(paymentSchedule.id);
|
||||||
|
if (isPrivileged() && (!displayOnceStatus || displayOnceStatus === paymentScheduleItem.id)) {
|
||||||
|
displayOnceMap.get(TypeOnce.SubscriptionCancel).set(paymentSchedule.id, paymentScheduleItem.id);
|
||||||
|
return (
|
||||||
|
<FabButton key={`cancel-subscription-${paymentSchedule.id}`}
|
||||||
|
onClick={toggleCancelSubscriptionModal}
|
||||||
|
icon={<i className="fas fa-times" />}>
|
||||||
|
{t('app.shared.payment_schedule_item_actions.cancel_subscription')}
|
||||||
|
</FabButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a button to confirm the receipt of the bank transfer, if the user is privileged enough
|
||||||
|
*/
|
||||||
|
const confirmTransferButton = (): ReactElement => {
|
||||||
|
if (isPrivileged()) {
|
||||||
|
return (
|
||||||
|
<FabButton key={`confirm-transfer-${paymentScheduleItem.id}`}
|
||||||
|
onClick={toggleConfirmTransferModal}
|
||||||
|
icon={<i className="fas fa-university"/>}>
|
||||||
|
{t('app.shared.payment_schedule_item_actions.confirm_payment')}
|
||||||
|
</FabButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a button to confirm the cashing of the check, if the user is privileged enough
|
||||||
|
*/
|
||||||
|
const confirmCheckButton = (): ReactElement => {
|
||||||
|
if (isPrivileged()) {
|
||||||
|
return (
|
||||||
|
<FabButton key={`confirm-check-${paymentScheduleItem.id}`}
|
||||||
|
onClick={toggleConfirmCashingModal}
|
||||||
|
icon={<i className="fas fa-check"/>}>
|
||||||
|
{t('app.shared.payment_schedule_item_actions.confirm_check')}
|
||||||
|
</FabButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a button to resolve the 3DS security check
|
||||||
|
*/
|
||||||
|
const solveActionButton = (): ReactElement => {
|
||||||
|
return (
|
||||||
|
<FabButton key={`solve-action-${paymentScheduleItem.id}`}
|
||||||
|
onClick={toggleResolveActionModal}
|
||||||
|
icon={<i className="fas fa-wrench"/>}>
|
||||||
|
{t('app.shared.payment_schedule_item_actions.resolve_action')}
|
||||||
|
</FabButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a button to update the default payment mean for the current payment schedule
|
||||||
|
*/
|
||||||
|
const updatePaymentMeanButton = (): ReactElement => {
|
||||||
|
const displayOnceStatus = displayOnceMap.get(TypeOnce.UpdatePaymentMean).get(paymentSchedule.id);
|
||||||
|
if (isPrivileged() && (!displayOnceStatus || displayOnceStatus === paymentScheduleItem.id)) {
|
||||||
|
displayOnceMap.get(TypeOnce.UpdatePaymentMean).set(paymentSchedule.id, paymentScheduleItem.id);
|
||||||
|
return (
|
||||||
|
<FabButton key={`update-payment-mean-${paymentScheduleItem.id}`}
|
||||||
|
onClick={toggleUpdatePaymentMeanModal}
|
||||||
|
icon={<i className="fas fa-money-bill-alt" />}>
|
||||||
|
{t('app.shared.payment_schedule_item_actions.update_payment_mean')}
|
||||||
|
</FabButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a button to update the credit card associated with the payment schedule
|
||||||
|
*/
|
||||||
|
const updateCardButton = (): ReactElement => {
|
||||||
|
const displayOnceStatus = displayOnceMap.get(TypeOnce.SubscriptionCancel).get(paymentSchedule.id);
|
||||||
|
if (isPrivileged() && (!displayOnceStatus || displayOnceStatus === paymentScheduleItem.id)) {
|
||||||
|
displayOnceMap.get(TypeOnce.CardUpdate).set(paymentSchedule.id, paymentScheduleItem.id);
|
||||||
|
return (
|
||||||
|
<FabButton key={`update-card-${paymentSchedule.id}`}
|
||||||
|
onClick={toggleUpdateCardModal}
|
||||||
|
icon={<i className="fas fa-credit-card"/>}>
|
||||||
|
{t('app.shared.payment_schedule_item_actions.update_card')}
|
||||||
|
</FabButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the actions button(s) for current paymentScheduleItem with state Pending
|
||||||
|
*/
|
||||||
|
const pendingActions = (): ReactElement => {
|
||||||
|
if (isPrivileged()) {
|
||||||
|
if (paymentSchedule.payment_method === PaymentMethod.Transfer) {
|
||||||
|
return confirmTransferButton();
|
||||||
|
}
|
||||||
|
if (paymentSchedule.payment_method === PaymentMethod.Check) {
|
||||||
|
return confirmCheckButton();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return <span>{t('app.shared.payment_schedule_item_actions.please_ask_reception')}</span>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the actions button(s) for current paymentScheduleItem with state Error or GatewayCanceled
|
||||||
|
*/
|
||||||
|
const errorActions = (): ReactElement[] => {
|
||||||
|
// if the payment schedule is canceled/in error, the schedule is over, and we can't update the card
|
||||||
|
displayOnceMap.get(TypeOnce.CardUpdate).set(paymentSchedule.id, paymentScheduleItem.id);
|
||||||
|
|
||||||
|
const buttons = [];
|
||||||
|
if (isPrivileged()) {
|
||||||
|
buttons.push(cancelSubscriptionButton());
|
||||||
|
buttons.push(updatePaymentMeanButton());
|
||||||
|
} else {
|
||||||
|
buttons.push(<span>{t('app.shared.payment_schedule_item_actions.please_ask_reception')}</span>);
|
||||||
|
}
|
||||||
|
return buttons;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the actions button(s) for current paymentScheduleItem with state New
|
||||||
|
*/
|
||||||
|
const newActions = (): Array<ReactElement> => {
|
||||||
|
const buttons = [];
|
||||||
|
if (paymentSchedule.payment_method === PaymentMethod.Card) {
|
||||||
|
buttons.push(updateCardButton());
|
||||||
|
}
|
||||||
|
if (isPrivileged()) {
|
||||||
|
buttons.push(cancelSubscriptionButton());
|
||||||
|
}
|
||||||
|
return buttons;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show/hide the modal dialog to cancel the current subscription
|
||||||
|
*/
|
||||||
|
const toggleCancelSubscriptionModal = (): void => {
|
||||||
|
setShowCancelSubscription(!showCancelSubscription);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show/hide the modal dialog that enable to confirm the cashing of the check for a given deadline.
|
||||||
|
*/
|
||||||
|
const toggleConfirmCashingModal = (): void => {
|
||||||
|
setShowConfirmCashing(!showConfirmCashing);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show/hide the modal dialog that enable to confirm the bank transfer for a given deadline.
|
||||||
|
*/
|
||||||
|
const toggleConfirmTransferModal = (): void => {
|
||||||
|
setShowConfirmTransfer(!showConfirmTransfer);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show/hide the modal dialog that trigger the card "action".
|
||||||
|
*/
|
||||||
|
const toggleResolveActionModal = (): void => {
|
||||||
|
setShowResolveAction(!showResolveAction);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable/disable the confirm button of the "action" modal
|
||||||
|
*/
|
||||||
|
const toggleConfirmActionButton = (): void => {
|
||||||
|
setConfirmActionDisabled(!isConfirmActionDisabled);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show/hide the modal dialog to update the bank card details
|
||||||
|
*/
|
||||||
|
const toggleUpdateCardModal = (): void => {
|
||||||
|
setShowUpdateCard(!showUpdateCard);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show/hide the modal dialog to update the payment mean
|
||||||
|
*/
|
||||||
|
const toggleUpdatePaymentMeanModal = (): void => {
|
||||||
|
setShowUpdatePaymentMean(!showUpdatePaymentMean);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After the user has confirmed that he wants to cash the check, update the API, refresh the list and close the modal.
|
||||||
|
*/
|
||||||
|
const onCheckCashingConfirmed = (): void => {
|
||||||
|
PaymentScheduleAPI.cashCheck(paymentScheduleItem.id).then((res) => {
|
||||||
|
if (res.state === PaymentScheduleItemState.Paid) {
|
||||||
|
onSuccess();
|
||||||
|
toggleConfirmCashingModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After the user has confirmed that he validates the transfer, update the API, refresh the list and close the modal.
|
||||||
|
*/
|
||||||
|
const onTransferConfirmed = (): void => {
|
||||||
|
PaymentScheduleAPI.confirmTransfer(paymentScheduleItem.id).then((res) => {
|
||||||
|
if (res.state === PaymentScheduleItemState.Paid) {
|
||||||
|
onSuccess();
|
||||||
|
toggleConfirmTransferModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the card was successfully updated, pay the invoice (using the new payment method) and close the modal
|
||||||
|
*/
|
||||||
|
const handleCardUpdateSuccess = (): void => {
|
||||||
|
if (paymentScheduleItem.state === PaymentScheduleItemState.RequirePaymentMethod) {
|
||||||
|
PaymentScheduleAPI.payItem(paymentScheduleItem.id).then(() => {
|
||||||
|
onSuccess();
|
||||||
|
onCardUpdateSuccess();
|
||||||
|
toggleUpdateCardModal();
|
||||||
|
}).catch((err) => {
|
||||||
|
onError(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// the user is updating his card number in a pro-active way, we don't need to trigger the payment
|
||||||
|
onCardUpdateSuccess();
|
||||||
|
toggleUpdateCardModal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the user has confirmed the cancellation, we transfer the request to the API
|
||||||
|
*/
|
||||||
|
const onCancelSubscriptionConfirmed = (): void => {
|
||||||
|
PaymentScheduleAPI.cancel(paymentSchedule.id).then(() => {
|
||||||
|
onSuccess();
|
||||||
|
toggleCancelSubscriptionModal();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After the 3DS confirmation was done (successfully or not), ask the API to refresh the item status,
|
||||||
|
* then refresh the list and close the modal
|
||||||
|
*/
|
||||||
|
const afterConfirmAction = (): void => {
|
||||||
|
toggleConfirmActionButton();
|
||||||
|
PaymentScheduleAPI.refreshItem(paymentScheduleItem.id).then(() => {
|
||||||
|
onSuccess();
|
||||||
|
toggleResolveActionModal();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the update of the payment mean was successful, refresh the list and close the modal
|
||||||
|
*/
|
||||||
|
const onPaymentMeanUpdateSuccess = (): void => {
|
||||||
|
onSuccess();
|
||||||
|
toggleUpdatePaymentMeanModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!show) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="payment-schedule-item-actions">
|
||||||
|
{paymentScheduleItem.state === PaymentScheduleItemState.Paid && downloadInvoiceButton(paymentScheduleItem.invoice_id)}
|
||||||
|
{paymentScheduleItem.state === PaymentScheduleItemState.Pending && pendingActions()}
|
||||||
|
{paymentScheduleItem.state === PaymentScheduleItemState.RequireAction && solveActionButton()}
|
||||||
|
{paymentScheduleItem.state === PaymentScheduleItemState.RequirePaymentMethod && updateCardButton()}
|
||||||
|
{paymentScheduleItem.state === PaymentScheduleItemState.Error && errorActions()}
|
||||||
|
{paymentScheduleItem.state === PaymentScheduleItemState.GatewayCanceled && errorActions()}
|
||||||
|
{paymentScheduleItem.state === PaymentScheduleItemState.New && newActions()}
|
||||||
|
<div className="modals">
|
||||||
|
{/* Confirm the cashing of the current deadline by check */}
|
||||||
|
<FabModal title={t('app.shared.payment_schedule_item_actions.confirm_check_cashing')}
|
||||||
|
isOpen={showConfirmCashing}
|
||||||
|
toggleModal={toggleConfirmCashingModal}
|
||||||
|
onConfirm={onCheckCashingConfirmed}
|
||||||
|
closeButton={true}
|
||||||
|
confirmButton={t('app.shared.payment_schedule_item_actions.confirm_button')}>
|
||||||
|
<span>
|
||||||
|
{t('app.shared.payment_schedule_item_actions.confirm_check_cashing_body', {
|
||||||
|
AMOUNT: FormatLib.price(paymentScheduleItem.amount),
|
||||||
|
DATE: FormatLib.date(paymentScheduleItem.due_date)
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</FabModal>
|
||||||
|
{/* Confirm the bank transfer for the current deadline */}
|
||||||
|
<FabModal title={t('app.shared.payment_schedule_item_actions.confirm_bank_transfer')}
|
||||||
|
isOpen={showConfirmTransfer}
|
||||||
|
toggleModal={toggleConfirmTransferModal}
|
||||||
|
onConfirm={onTransferConfirmed}
|
||||||
|
closeButton={true}
|
||||||
|
confirmButton={t('app.shared.payment_schedule_item_actions.confirm_button')}>
|
||||||
|
<span>
|
||||||
|
{t('app.shared.payment_schedule_item_actions.confirm_bank_transfer_body', {
|
||||||
|
AMOUNT: FormatLib.price(paymentScheduleItem.amount),
|
||||||
|
DATE: FormatLib.date(paymentScheduleItem.due_date)
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</FabModal>
|
||||||
|
{/* Cancel the subscription */}
|
||||||
|
<FabModal title={t('app.shared.payment_schedule_item_actions.cancel_subscription')}
|
||||||
|
isOpen={showCancelSubscription}
|
||||||
|
toggleModal={toggleCancelSubscriptionModal}
|
||||||
|
onConfirm={onCancelSubscriptionConfirmed}
|
||||||
|
closeButton={true}
|
||||||
|
confirmButton={t('app.shared.payment_schedule_item_actions.confirm_button')}>
|
||||||
|
{t('app.shared.payment_schedule_item_actions.confirm_cancel_subscription')}
|
||||||
|
</FabModal>
|
||||||
|
{/* 3D secure confirmation */}
|
||||||
|
<StripeConfirmModal isOpen={showResolveAction}
|
||||||
|
toggleModal={toggleResolveActionModal}
|
||||||
|
onSuccess={afterConfirmAction}
|
||||||
|
paymentScheduleItemId={paymentScheduleItem.id} />
|
||||||
|
{/* Update credit card */}
|
||||||
|
<UpdateCardModal isOpen={showUpdateCard}
|
||||||
|
toggleModal={toggleUpdateCardModal}
|
||||||
|
operator={operator}
|
||||||
|
afterSuccess={handleCardUpdateSuccess}
|
||||||
|
onError={onError}
|
||||||
|
schedule={paymentSchedule}>
|
||||||
|
</UpdateCardModal>
|
||||||
|
{/* Update the payment mean */}
|
||||||
|
<UpdatePaymentMeanModal isOpen={showUpdatePaymentMean}
|
||||||
|
toggleModal={toggleUpdatePaymentMeanModal}
|
||||||
|
onError={onError}
|
||||||
|
afterSuccess={onPaymentMeanUpdateSuccess}
|
||||||
|
paymentSchedule={paymentSchedule} />
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
PaymentScheduleItemActions.defaultProps = { show: false };
|
@ -2,15 +2,15 @@ import React, { ReactEventHandler, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Loader } from '../base/loader';
|
import { Loader } from '../base/loader';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { FabButton } from '../base/fab-button';
|
import { User } from '../../models/user';
|
||||||
import { FabModal } from '../base/fab-modal';
|
import {
|
||||||
import { UpdateCardModal } from '../payment/update-card-modal';
|
PaymentSchedule,
|
||||||
import { StripeElements } from '../payment/stripe/stripe-elements';
|
PaymentScheduleItem,
|
||||||
import { StripeConfirm } from '../payment/stripe/stripe-confirm';
|
PaymentScheduleItemState
|
||||||
import { User, UserRole } from '../../models/user';
|
} from '../../models/payment-schedule';
|
||||||
import { PaymentSchedule, PaymentScheduleItem, PaymentScheduleItemState } from '../../models/payment-schedule';
|
|
||||||
import PaymentScheduleAPI from '../../api/payment-schedule';
|
|
||||||
import FormatLib from '../../lib/format';
|
import FormatLib from '../../lib/format';
|
||||||
|
import { PaymentScheduleItemActions, TypeOnce } from './payment-schedule-item-actions';
|
||||||
|
import { StripeElements } from '../payment/stripe/stripe-elements';
|
||||||
|
|
||||||
interface PaymentSchedulesTableProps {
|
interface PaymentSchedulesTableProps {
|
||||||
paymentSchedules: Array<PaymentSchedule>,
|
paymentSchedules: Array<PaymentSchedule>,
|
||||||
@ -29,23 +29,12 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
|
|||||||
|
|
||||||
// for each payment schedule: are the details (all deadlines) shown or hidden?
|
// for each payment schedule: are the details (all deadlines) shown or hidden?
|
||||||
const [showExpanded, setShowExpanded] = useState<Map<number, boolean>>(new Map());
|
const [showExpanded, setShowExpanded] = useState<Map<number, boolean>>(new Map());
|
||||||
// is open, the modal dialog to confirm the cashing of a check?
|
// we want to display some buttons only once. This map keep track of the buttons that have been displayed.
|
||||||
const [showConfirmCashing, setShowConfirmCashing] = useState<boolean>(false);
|
const [displayOnceMap] = useState<Map<TypeOnce, Map<number, number>>>(new Map([
|
||||||
// is open, the modal dialog the resolve a pending card payment?
|
[TypeOnce.SubscriptionCancel, new Map()],
|
||||||
const [showResolveAction, setShowResolveAction] = useState<boolean>(false);
|
[TypeOnce.CardUpdate, new Map()],
|
||||||
// the user cannot confirm the action modal (3D secure), unless he has resolved the pending action
|
[TypeOnce.UpdatePaymentMean, new Map()]
|
||||||
const [isConfirmActionDisabled, setConfirmActionDisabled] = useState<boolean>(true);
|
]));
|
||||||
// is open, the modal dialog to update the card details
|
|
||||||
const [showUpdateCard, setShowUpdateCard] = useState<boolean>(false);
|
|
||||||
// when an action is triggered on a deadline, the deadline is saved here until the action is done or cancelled.
|
|
||||||
const [tempDeadline, setTempDeadline] = useState<PaymentScheduleItem>(null);
|
|
||||||
// when an action is triggered on a deadline, the parent schedule is saved here until the action is done or cancelled.
|
|
||||||
const [tempSchedule, setTempSchedule] = useState<PaymentSchedule>(null);
|
|
||||||
// is open, the modal dialog to cancel the associated subscription?
|
|
||||||
const [showCancelSubscription, setShowCancelSubscription] = useState<boolean>(false);
|
|
||||||
|
|
||||||
// we want to display the card update button, only once. This is an association table keeping when we already shown one
|
|
||||||
const cardUpdateButton = new Map<number, boolean>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the requested payment schedule is displayed with its deadlines (PaymentScheduleItem) or without them
|
* Check if the requested payment schedule is displayed with its deadlines (PaymentScheduleItem) or without them
|
||||||
@ -89,19 +78,11 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* For use with downloadButton()
|
|
||||||
*/
|
|
||||||
enum TargetType {
|
|
||||||
Invoice = 'invoices',
|
|
||||||
PaymentSchedule = 'payment_schedules'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a button to download a PDF file, may be an invoice, or a payment schedule, depending or the provided parameters
|
* Return a button to download a PDF file, may be an invoice, or a payment schedule, depending or the provided parameters
|
||||||
*/
|
*/
|
||||||
const downloadButton = (target: TargetType, id: number): JSX.Element => {
|
const downloadScheduleButton = (id: number): JSX.Element => {
|
||||||
const link = `api/${target}/${id}/download`;
|
const link = `api/payment_schedules/${id}/download`;
|
||||||
return (
|
return (
|
||||||
<a href={link} target="_blank" className="download-button" rel="noreferrer">
|
<a href={link} target="_blank" className="download-button" rel="noreferrer">
|
||||||
<i className="fas fa-download" />
|
<i className="fas fa-download" />
|
||||||
@ -113,8 +94,8 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
|
|||||||
/**
|
/**
|
||||||
* Return the human-readable string for the status of the provided deadline.
|
* Return the human-readable string for the status of the provided deadline.
|
||||||
*/
|
*/
|
||||||
const formatState = (item: PaymentScheduleItem): JSX.Element => {
|
const formatState = (item: PaymentScheduleItem, schedule: PaymentSchedule): JSX.Element => {
|
||||||
let res = t(`app.shared.schedules_table.state_${item.state}`);
|
let res = t(`app.shared.schedules_table.state_${item.state}${item.state === 'pending' ? '_' + schedule.payment_method : ''}`);
|
||||||
if (item.state === PaymentScheduleItemState.Paid) {
|
if (item.state === PaymentScheduleItemState.Paid) {
|
||||||
const key = `app.shared.schedules_table.method_${item.payment_method}`;
|
const key = `app.shared.schedules_table.method_${item.payment_method}`;
|
||||||
res += ` (${t(key)})`;
|
res += ` (${t(key)})`;
|
||||||
@ -122,96 +103,6 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
|
|||||||
return <span className={`state-${item.state}`}>{res}</span>;
|
return <span className={`state-${item.state}`}>{res}</span>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the current operator has administrative rights or is a normal member
|
|
||||||
*/
|
|
||||||
const isPrivileged = (): boolean => {
|
|
||||||
return (operator.role === UserRole.Admin || operator.role === UserRole.Manager);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the action button(s) for the given deadline
|
|
||||||
*/
|
|
||||||
const itemButtons = (item: PaymentScheduleItem, schedule: PaymentSchedule): JSX.Element => {
|
|
||||||
switch (item.state) {
|
|
||||||
case PaymentScheduleItemState.Paid:
|
|
||||||
return downloadButton(TargetType.Invoice, item.invoice_id);
|
|
||||||
case PaymentScheduleItemState.Pending:
|
|
||||||
if (isPrivileged()) {
|
|
||||||
return (
|
|
||||||
<FabButton onClick={handleConfirmCheckPayment(item)}
|
|
||||||
icon={<i className="fas fa-money-check" />}>
|
|
||||||
{t('app.shared.schedules_table.confirm_payment')}
|
|
||||||
</FabButton>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return <span>{t('app.shared.schedules_table.please_ask_reception')}</span>;
|
|
||||||
}
|
|
||||||
case PaymentScheduleItemState.RequireAction:
|
|
||||||
return (
|
|
||||||
<FabButton onClick={handleSolveAction(item)}
|
|
||||||
icon={<i className="fas fa-wrench" />}>
|
|
||||||
{t('app.shared.schedules_table.solve')}
|
|
||||||
</FabButton>
|
|
||||||
);
|
|
||||||
case PaymentScheduleItemState.RequirePaymentMethod:
|
|
||||||
return (
|
|
||||||
<FabButton onClick={handleUpdateCard(schedule, item)}
|
|
||||||
icon={<i className="fas fa-credit-card" />}>
|
|
||||||
{t('app.shared.schedules_table.update_card')}
|
|
||||||
</FabButton>
|
|
||||||
);
|
|
||||||
case PaymentScheduleItemState.Error:
|
|
||||||
// if the payment is in error, the schedule is over, and we can't update the card
|
|
||||||
cardUpdateButton.set(schedule.id, true);
|
|
||||||
if (isPrivileged()) {
|
|
||||||
return (
|
|
||||||
<FabButton onClick={handleCancelSubscription(schedule)}
|
|
||||||
icon={<i className="fas fa-times" />}>
|
|
||||||
{t('app.shared.schedules_table.cancel_subscription')}
|
|
||||||
</FabButton>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return <span>{t('app.shared.schedules_table.please_ask_reception')}</span>;
|
|
||||||
}
|
|
||||||
case PaymentScheduleItemState.New:
|
|
||||||
if (!cardUpdateButton.get(schedule.id)) {
|
|
||||||
cardUpdateButton.set(schedule.id, true);
|
|
||||||
return (
|
|
||||||
<FabButton onClick={handleUpdateCard(schedule)}
|
|
||||||
icon={<i className="fas fa-credit-card" />}>
|
|
||||||
{t('app.shared.schedules_table.update_card')}
|
|
||||||
</FabButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <span />;
|
|
||||||
default:
|
|
||||||
return <span />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback triggered when the user's clicks on the "cash check" button: show a confirmation modal
|
|
||||||
*/
|
|
||||||
const handleConfirmCheckPayment = (item: PaymentScheduleItem): ReactEventHandler => {
|
|
||||||
return (): void => {
|
|
||||||
setTempDeadline(item);
|
|
||||||
toggleConfirmCashingModal();
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* After the user has confirmed that he wants to cash the check, update the API, refresh the list and close the modal.
|
|
||||||
*/
|
|
||||||
const onCheckCashingConfirmed = (): void => {
|
|
||||||
PaymentScheduleAPI.cashCheck(tempDeadline.id).then((res) => {
|
|
||||||
if (res.state === PaymentScheduleItemState.Paid) {
|
|
||||||
refreshSchedulesTable();
|
|
||||||
toggleConfirmCashingModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh all payment schedules in the table
|
* Refresh all payment schedules in the table
|
||||||
*/
|
*/
|
||||||
@ -219,121 +110,9 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
|
|||||||
refreshList();
|
refreshList();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Show/hide the modal dialog that enable to confirm the cashing of the check for a given deadline.
|
|
||||||
*/
|
|
||||||
const toggleConfirmCashingModal = (): void => {
|
|
||||||
setShowConfirmCashing(!showConfirmCashing);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show/hide the modal dialog that trigger the card "action".
|
|
||||||
*/
|
|
||||||
const toggleResolveActionModal = (): void => {
|
|
||||||
setShowResolveAction(!showResolveAction);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback triggered when the user's clicks on the "resolve" button: show a modal that will trigger the action
|
|
||||||
*/
|
|
||||||
const handleSolveAction = (item: PaymentScheduleItem): ReactEventHandler => {
|
|
||||||
return (): void => {
|
|
||||||
setTempDeadline(item);
|
|
||||||
toggleResolveActionModal();
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* After the action was done (successfully or not), ask the API to refresh the item status, then refresh the list and close the modal
|
|
||||||
*/
|
|
||||||
const afterAction = (): void => {
|
|
||||||
toggleConfirmActionButton();
|
|
||||||
PaymentScheduleAPI.refreshItem(tempDeadline.id).then(() => {
|
|
||||||
refreshSchedulesTable();
|
|
||||||
toggleResolveActionModal();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable/disable the confirm button of the "action" modal
|
|
||||||
*/
|
|
||||||
const toggleConfirmActionButton = (): void => {
|
|
||||||
setConfirmActionDisabled(!isConfirmActionDisabled);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback triggered when the user's clicks on the "update card" button: show a modal to input a new card
|
|
||||||
*/
|
|
||||||
const handleUpdateCard = (paymentSchedule: PaymentSchedule, item?: PaymentScheduleItem): ReactEventHandler => {
|
|
||||||
return (): void => {
|
|
||||||
setTempDeadline(item);
|
|
||||||
setTempSchedule(paymentSchedule);
|
|
||||||
toggleUpdateCardModal();
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show/hide the modal dialog to update the bank card details
|
|
||||||
*/
|
|
||||||
const toggleUpdateCardModal = (): void => {
|
|
||||||
setShowUpdateCard(!showUpdateCard);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the card was successfully updated, pay the invoice (using the new payment method) and close the modal
|
|
||||||
*/
|
|
||||||
const handleCardUpdateSuccess = (): void => {
|
|
||||||
if (tempDeadline) {
|
|
||||||
PaymentScheduleAPI.payItem(tempDeadline.id).then(() => {
|
|
||||||
refreshSchedulesTable();
|
|
||||||
onCardUpdateSuccess();
|
|
||||||
toggleUpdateCardModal();
|
|
||||||
}).catch((err) => {
|
|
||||||
handleCardUpdateError(err);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// if no tempDeadline (i.e. PaymentScheduleItem), then the user is updating his card number in a pro-active way, we don't need to trigger the payment
|
|
||||||
onCardUpdateSuccess();
|
|
||||||
toggleUpdateCardModal();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the card was not updated, raise the error
|
|
||||||
*/
|
|
||||||
const handleCardUpdateError = (error): void => {
|
|
||||||
onError(error);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback triggered when the user clicks on the "cancel subscription" button
|
|
||||||
*/
|
|
||||||
const handleCancelSubscription = (schedule: PaymentSchedule): ReactEventHandler => {
|
|
||||||
return (): void => {
|
|
||||||
setTempSchedule(schedule);
|
|
||||||
toggleCancelSubscriptionModal();
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show/hide the modal dialog to cancel the current subscription
|
|
||||||
*/
|
|
||||||
const toggleCancelSubscriptionModal = (): void => {
|
|
||||||
setShowCancelSubscription(!showCancelSubscription);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the user has confirmed the cancellation, we transfer the request to the API
|
|
||||||
*/
|
|
||||||
const onCancelSubscriptionConfirmed = (): void => {
|
|
||||||
PaymentScheduleAPI.cancel(tempSchedule.id).then(() => {
|
|
||||||
refreshSchedulesTable();
|
|
||||||
toggleCancelSubscriptionModal();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<StripeElements>
|
||||||
<table className="schedules-table">
|
<table className="schedules-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -356,7 +135,7 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
|
|||||||
<td className="w-200">{FormatLib.date(_.minBy(p.items, 'due_date').due_date)}</td>
|
<td className="w-200">{FormatLib.date(_.minBy(p.items, 'due_date').due_date)}</td>
|
||||||
<td className="w-120">{FormatLib.price(p.total)}</td>
|
<td className="w-120">{FormatLib.price(p.total)}</td>
|
||||||
{showCustomer && <td className="w-200">{p.user.name}</td>}
|
{showCustomer && <td className="w-200">{p.user.name}</td>}
|
||||||
<td className="w-200">{downloadButton(TargetType.PaymentSchedule, p.id)}</td>
|
<td className="w-200">{downloadScheduleButton(p.id)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style={{ display: statusDisplay(p.id) }}>
|
<tr style={{ display: statusDisplay(p.id) }}>
|
||||||
<td className="w-35" />
|
<td className="w-35" />
|
||||||
@ -375,8 +154,17 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
|
|||||||
{_.orderBy(p.items, 'due_date').map(item => <tr key={item.id}>
|
{_.orderBy(p.items, 'due_date').map(item => <tr key={item.id}>
|
||||||
<td>{FormatLib.date(item.due_date)}</td>
|
<td>{FormatLib.date(item.due_date)}</td>
|
||||||
<td>{FormatLib.price(item.amount)}</td>
|
<td>{FormatLib.price(item.amount)}</td>
|
||||||
<td>{formatState(item)}</td>
|
<td>{formatState(item, p)}</td>
|
||||||
<td>{itemButtons(item, p)}</td>
|
<td>
|
||||||
|
<PaymentScheduleItemActions paymentScheduleItem={item}
|
||||||
|
paymentSchedule={p}
|
||||||
|
onError={onError}
|
||||||
|
onSuccess={refreshSchedulesTable}
|
||||||
|
onCardUpdateSuccess={onCardUpdateSuccess}
|
||||||
|
operator={operator}
|
||||||
|
displayOnceMap={displayOnceMap}
|
||||||
|
show={isExpanded(p.id)}/>
|
||||||
|
</td>
|
||||||
</tr>)}
|
</tr>)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -389,47 +177,8 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
|
|||||||
</tr>)}
|
</tr>)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div className="modals">
|
|
||||||
<FabModal title={t('app.shared.schedules_table.confirm_check_cashing')}
|
|
||||||
isOpen={showConfirmCashing}
|
|
||||||
toggleModal={toggleConfirmCashingModal}
|
|
||||||
onConfirm={onCheckCashingConfirmed}
|
|
||||||
closeButton={true}
|
|
||||||
confirmButton={t('app.shared.schedules_table.confirm_button')}>
|
|
||||||
{tempDeadline && <span>
|
|
||||||
{t('app.shared.schedules_table.confirm_check_cashing_body', {
|
|
||||||
AMOUNT: FormatLib.price(tempDeadline.amount),
|
|
||||||
DATE: FormatLib.date(tempDeadline.due_date)
|
|
||||||
})}
|
|
||||||
</span>}
|
|
||||||
</FabModal>
|
|
||||||
<FabModal title={t('app.shared.schedules_table.cancel_subscription')}
|
|
||||||
isOpen={showCancelSubscription}
|
|
||||||
toggleModal={toggleCancelSubscriptionModal}
|
|
||||||
onConfirm={onCancelSubscriptionConfirmed}
|
|
||||||
closeButton={true}
|
|
||||||
confirmButton={t('app.shared.schedules_table.confirm_button')}>
|
|
||||||
{t('app.shared.schedules_table.confirm_cancel_subscription')}
|
|
||||||
</FabModal>
|
|
||||||
<StripeElements>
|
|
||||||
<FabModal title={t('app.shared.schedules_table.resolve_action')}
|
|
||||||
isOpen={showResolveAction}
|
|
||||||
toggleModal={toggleResolveActionModal}
|
|
||||||
onConfirm={afterAction}
|
|
||||||
confirmButton={t('app.shared.schedules_table.ok_button')}
|
|
||||||
preventConfirm={isConfirmActionDisabled}>
|
|
||||||
{tempDeadline && <StripeConfirm clientSecret={tempDeadline.client_secret} onResponse={toggleConfirmActionButton} />}
|
|
||||||
</FabModal>
|
|
||||||
{tempSchedule && <UpdateCardModal isOpen={showUpdateCard}
|
|
||||||
toggleModal={toggleUpdateCardModal}
|
|
||||||
operator={operator}
|
|
||||||
afterSuccess={handleCardUpdateSuccess}
|
|
||||||
onError={handleCardUpdateError}
|
|
||||||
schedule={tempSchedule}>
|
|
||||||
</UpdateCardModal>}
|
|
||||||
</StripeElements>
|
</StripeElements>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
PaymentSchedulesTableComponent.defaultProps = { showCustomer: false };
|
PaymentSchedulesTableComponent.defaultProps = { showCustomer: false };
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Select from 'react-select';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FabModal } from '../base/fab-modal';
|
||||||
|
import { PaymentMethod, PaymentSchedule } from '../../models/payment-schedule';
|
||||||
|
import PaymentScheduleAPI from '../../api/payment-schedule';
|
||||||
|
|
||||||
|
interface UpdatePaymentMeanModalProps {
|
||||||
|
isOpen: boolean,
|
||||||
|
toggleModal: () => void,
|
||||||
|
onError: (message: string) => void,
|
||||||
|
afterSuccess: () => void,
|
||||||
|
paymentSchedule: PaymentSchedule
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option format, expected by react-select
|
||||||
|
* @see https://github.com/JedWatson/react-select
|
||||||
|
*/
|
||||||
|
type selectOption = { value: PaymentMethod, label: string };
|
||||||
|
|
||||||
|
export const UpdatePaymentMeanModal: React.FC<UpdatePaymentMeanModalProps> = ({ isOpen, toggleModal, onError, afterSuccess, paymentSchedule }) => {
|
||||||
|
const { t } = useTranslation('admin');
|
||||||
|
|
||||||
|
const [paymentMean, setPaymentMean] = React.useState<PaymentMethod>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert all payment means to the react-select format
|
||||||
|
*/
|
||||||
|
const buildOptions = (): Array<selectOption> => {
|
||||||
|
return Object.keys(PaymentMethod).filter(pm => PaymentMethod[pm] !== PaymentMethod.Card).map(pm => {
|
||||||
|
return { value: PaymentMethod[pm], label: t(`app.admin.update_payment_mean_modal.method_${pm}`) };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the payment mean is changed in the select, update the state
|
||||||
|
*/
|
||||||
|
const handleMeanSelected = (option: selectOption): void => {
|
||||||
|
setPaymentMean(option.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the user clicks on the update button, update the default payment mean for the given payment schedule
|
||||||
|
*/
|
||||||
|
const handlePaymentMeanUpdate = (): void => {
|
||||||
|
PaymentScheduleAPI.update({
|
||||||
|
id: paymentSchedule.id,
|
||||||
|
payment_method: paymentMean
|
||||||
|
}).then(() => {
|
||||||
|
afterSuccess();
|
||||||
|
}).catch(error => {
|
||||||
|
onError(error.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FabModal isOpen={isOpen}
|
||||||
|
className="update-payment-mean-modal"
|
||||||
|
title={t('app.admin.update_payment_mean_modal.title')}
|
||||||
|
confirmButton={t('app.admin.update_payment_mean_modal.confirm_button')}
|
||||||
|
onConfirm={handlePaymentMeanUpdate}
|
||||||
|
toggleModal={toggleModal}
|
||||||
|
closeButton={true}>
|
||||||
|
<span>{t('app.admin.update_payment_mean_modal.update_info')}</span>
|
||||||
|
<Select className="payment-mean-select"
|
||||||
|
placeholder={t('app.admin.update_payment_mean_modal.select_payment_mean')}
|
||||||
|
id="payment-mean"
|
||||||
|
onChange={handleMeanSelected}
|
||||||
|
options={buildOptions()}></Select>
|
||||||
|
</FabModal>
|
||||||
|
);
|
||||||
|
};
|
@ -55,7 +55,7 @@ interface AbstractPaymentModalProps {
|
|||||||
/**
|
/**
|
||||||
* This component is an abstract modal that must be extended by each payment gateway to include its payment form.
|
* This component is an abstract modal that must be extended by each payment gateway to include its payment form.
|
||||||
*
|
*
|
||||||
* This component must not be called directly but must be extended for each implemented payment gateway
|
* This component must not be called directly but must be extended for each implemented payment gateway.
|
||||||
* @see https://reactjs.org/docs/composition-vs-inheritance.html
|
* @see https://reactjs.org/docs/composition-vs-inheritance.html
|
||||||
*/
|
*/
|
||||||
export const AbstractPaymentModal: React.FC<AbstractPaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, cart, updateCart, currentUser, schedule, customer, logoFooter, GatewayForm, formId, className, formClassName, title, preventCgv, preventScheduleInfo, modalSize }) => {
|
export const AbstractPaymentModal: React.FC<AbstractPaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, cart, updateCart, currentUser, schedule, customer, logoFooter, GatewayForm, formId, className, formClassName, title, preventCgv, preventScheduleInfo, modalSize }) => {
|
||||||
|
@ -8,9 +8,9 @@ import SettingAPI from '../../../api/setting';
|
|||||||
import { SettingName } from '../../../models/setting';
|
import { SettingName } from '../../../models/setting';
|
||||||
import { PaymentModal } from '../payment-modal';
|
import { PaymentModal } from '../payment-modal';
|
||||||
import { PaymentSchedule } from '../../../models/payment-schedule';
|
import { PaymentSchedule } from '../../../models/payment-schedule';
|
||||||
import { PaymentMethod } from '../../../models/payment';
|
import { HtmlTranslate } from '../../base/html-translate';
|
||||||
|
|
||||||
const ALL_SCHEDULE_METHODS = ['card', 'check'] as const;
|
const ALL_SCHEDULE_METHODS = ['card', 'check', 'transfer'] as const;
|
||||||
type scheduleMethod = typeof ALL_SCHEDULE_METHODS[number];
|
type scheduleMethod = typeof ALL_SCHEDULE_METHODS[number];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,11 +31,7 @@ export const LocalPaymentForm: React.FC<GatewayFormProps> = ({ onSubmit, onSucce
|
|||||||
const [onlinePaymentModal, setOnlinePaymentModal] = useState<boolean>(false);
|
const [onlinePaymentModal, setOnlinePaymentModal] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cart.payment_method === PaymentMethod.Card) {
|
setMethod(cart.payment_method || 'check');
|
||||||
setMethod('card');
|
|
||||||
} else {
|
|
||||||
setMethod('check');
|
|
||||||
}
|
|
||||||
}, [cart]);
|
}, [cart]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,11 +61,7 @@ export const LocalPaymentForm: React.FC<GatewayFormProps> = ({ onSubmit, onSucce
|
|||||||
* Callback triggered when the user selects a payment method for the current payment schedule.
|
* Callback triggered when the user selects a payment method for the current payment schedule.
|
||||||
*/
|
*/
|
||||||
const handleUpdateMethod = (option: selectOption) => {
|
const handleUpdateMethod = (option: selectOption) => {
|
||||||
if (option.value === 'card') {
|
updateCart(Object.assign({}, cart, { payment_method: option.value }));
|
||||||
updateCart(Object.assign({}, cart, { payment_method: PaymentMethod.Card }));
|
|
||||||
} else {
|
|
||||||
updateCart(Object.assign({}, cart, { payment_method: PaymentMethod.Other }));
|
|
||||||
}
|
|
||||||
setMethod(option.value);
|
setMethod(option.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -140,6 +132,7 @@ export const LocalPaymentForm: React.FC<GatewayFormProps> = ({ onSubmit, onSucce
|
|||||||
value={methodToOption(method)} />
|
value={methodToOption(method)} />
|
||||||
{method === 'card' && <p>{t('app.admin.local_payment.card_collection_info')}</p>}
|
{method === 'card' && <p>{t('app.admin.local_payment.card_collection_info')}</p>}
|
||||||
{method === 'check' && <p>{t('app.admin.local_payment.check_collection_info', { DEADLINES: paymentSchedule.items.length })}</p>}
|
{method === 'check' && <p>{t('app.admin.local_payment.check_collection_info', { DEADLINES: paymentSchedule.items.length })}</p>}
|
||||||
|
{method === 'transfer' && <HtmlTranslate trKey="app.admin.local_payment.transfer_collection_info" options={{ DEADLINES: paymentSchedule.items.length }} />}
|
||||||
</div>
|
</div>
|
||||||
<div className="full-schedule">
|
<div className="full-schedule">
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
import { StripeConfirm } from './stripe-confirm';
|
||||||
|
import { FabModal } from '../../base/fab-modal';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import PaymentScheduleAPI from '../../../api/payment-schedule';
|
||||||
|
import { PaymentScheduleItem } from '../../../models/payment-schedule';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
interface StripeConfirmModalProps {
|
||||||
|
isOpen: boolean,
|
||||||
|
toggleModal: () => void,
|
||||||
|
onSuccess: () => void,
|
||||||
|
paymentScheduleItemId: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal dialog that trigger a 3D secure confirmation for the given payment schedule item (deadline for a payment schedule).
|
||||||
|
*/
|
||||||
|
export const StripeConfirmModal: React.FC<StripeConfirmModalProps> = ({ isOpen, toggleModal, onSuccess, paymentScheduleItemId }) => {
|
||||||
|
const { t } = useTranslation('shared');
|
||||||
|
|
||||||
|
const [item, setItem] = useState<PaymentScheduleItem>(null);
|
||||||
|
const [isPending, setIsPending] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
PaymentScheduleAPI.getItem(paymentScheduleItemId).then(data => {
|
||||||
|
setItem(data);
|
||||||
|
});
|
||||||
|
}, [paymentScheduleItemId]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback triggered when the confirm button was clicked in the modal.
|
||||||
|
*/
|
||||||
|
const onConfirmed = (): void => {
|
||||||
|
togglePending();
|
||||||
|
onSuccess();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable/disable the confirm button of the "action" modal
|
||||||
|
*/
|
||||||
|
const togglePending = (): void => {
|
||||||
|
setIsPending(!isPending);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FabModal title={t('app.shared.schedules_table.resolve_action')}
|
||||||
|
isOpen={isOpen}
|
||||||
|
toggleModal={toggleModal}
|
||||||
|
onConfirm={onConfirmed}
|
||||||
|
confirmButton={t('app.shared.schedules_table.ok_button')}
|
||||||
|
preventConfirm={isPending}>
|
||||||
|
{item && <StripeConfirm clientSecret={item.client_secret} onResponse={togglePending} />}
|
||||||
|
</FabModal>
|
||||||
|
);
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
import React, { memo, useEffect, useState } from 'react';
|
import React, { memo, useEffect, useState } from 'react';
|
||||||
import { Elements } from '@stripe/react-stripe-js';
|
import { Elements } from '@stripe/react-stripe-js';
|
||||||
import { loadStripe } from '@stripe/stripe-js';
|
import { loadStripe, Stripe } from '@stripe/stripe-js';
|
||||||
import { SettingName } from '../../../models/setting';
|
import { SettingName } from '../../../models/setting';
|
||||||
import SettingAPI from '../../../api/setting';
|
import SettingAPI from '../../../api/setting';
|
||||||
|
|
||||||
@ -8,15 +8,17 @@ import SettingAPI from '../../../api/setting';
|
|||||||
* This component initializes the stripe's Elements tag with the API key
|
* This component initializes the stripe's Elements tag with the API key
|
||||||
*/
|
*/
|
||||||
export const StripeElements: React.FC = memo(({ children }) => {
|
export const StripeElements: React.FC = memo(({ children }) => {
|
||||||
const [stripe, setStripe] = useState(undefined);
|
const [stripe, setStripe] = useState<Promise<Stripe | null>>(undefined);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When this component is mounted, we initialize the <Elements> tag with the Stripe's public key
|
* When this component is mounted, we initialize the <Elements> tag with the Stripe's public key
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
SettingAPI.get(SettingName.StripePublicKey).then(key => {
|
SettingAPI.get(SettingName.StripePublicKey).then(key => {
|
||||||
|
if (key?.value) {
|
||||||
const promise = loadStripe(key.value);
|
const promise = loadStripe(key.value);
|
||||||
setStripe(promise);
|
setStripe(promise);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -24,11 +24,7 @@ const UpdateCardModalComponent: React.FC<UpdateCardModalProps> = ({ isOpen, togg
|
|||||||
const [gateway, setGateway] = useState<string>('');
|
const [gateway, setGateway] = useState<string>('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (schedule.gateway_subscription.classname.match(/^PayZen::/)) {
|
setGateway(schedule.gateway);
|
||||||
setGateway('payzen');
|
|
||||||
} else if (schedule.gateway_subscription.classname.match(/^Stripe::/)) {
|
|
||||||
setGateway('stripe');
|
|
||||||
}
|
|
||||||
}, [schedule]);
|
}, [schedule]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,7 +40,7 @@ const UpdateCardModalComponent: React.FC<UpdateCardModalProps> = ({ isOpen, togg
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the PayZen update-card modal
|
* Render the PayZen update-card modal
|
||||||
*/ // 1
|
*/
|
||||||
const renderPayZenModal = (): ReactElement => {
|
const renderPayZenModal = (): ReactElement => {
|
||||||
return <PayzenCardUpdateModal isOpen={isOpen}
|
return <PayzenCardUpdateModal isOpen={isOpen}
|
||||||
toggleModal={toggleModal}
|
toggleModal={toggleModal}
|
||||||
@ -58,15 +54,16 @@ const UpdateCardModalComponent: React.FC<UpdateCardModalProps> = ({ isOpen, togg
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
switch (gateway) {
|
switch (gateway) {
|
||||||
case 'stripe':
|
case 'Stripe':
|
||||||
return renderStripeModal();
|
return renderStripeModal();
|
||||||
case 'payzen':
|
case 'PayZen':
|
||||||
return renderPayZenModal();
|
return renderPayZenModal();
|
||||||
case '':
|
case '':
|
||||||
|
case undefined:
|
||||||
return <div/>;
|
return <div/>;
|
||||||
default:
|
default:
|
||||||
onError(t('app.shared.update_card_modal.unexpected_error'));
|
onError(t('app.shared.update_card_modal.unexpected_error'));
|
||||||
console.error(`[UpdateCardModal] unexpected gateway: ${schedule.gateway_subscription?.classname}`);
|
console.error(`[UpdateCardModal] unexpected gateway: ${schedule.gateway} for schedule ${schedule.id}`);
|
||||||
return <div />;
|
return <div />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -35,7 +35,7 @@ const EditPlanCategoryComponent: React.FC<EditPlanCategoryProps> = ({ onSuccess,
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The edit has been confirmed by the user.
|
* The edit has been confirmed by the user.
|
||||||
* Call the API to trigger the update of the temporary set plan-category
|
* Call the API to trigger the update of the temporary set plan-category.
|
||||||
*/
|
*/
|
||||||
const onEditConfirmed = (): void => {
|
const onEditConfirmed = (): void => {
|
||||||
PlanCategoryAPI.update(tempCategory).then((updatedCategory) => {
|
PlanCategoryAPI.update(tempCategory).then((updatedCategory) => {
|
||||||
|
@ -28,10 +28,10 @@ export const ConfigureExtendedPriceButton: React.FC<ConfigureExtendedPriceButton
|
|||||||
const [showList, setShowList] = useState<boolean>(false);
|
const [showList, setShowList] = useState<boolean>(false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the number of minutes, user-friendly formatted
|
* Return the number of hours, user-friendly formatted
|
||||||
*/
|
*/
|
||||||
const formatDuration = (minutes: number): string => {
|
const formatDuration = (minutes: number): string => {
|
||||||
return t('app.admin.configure_extended_prices_button.extended_price_DURATION', { DURATION: minutes });
|
return t('app.admin.configure_extended_prices_button.extended_price_DURATION', { DURATION: minutes / 60 });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,11 +40,11 @@ export const ExtendedPriceForm: React.FC<ExtendedPriceFormProps> = ({ formId, on
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback triggered when the user inputs a number of minutes for the current extended price.
|
* Callback triggered when the user inputs a number of hours for the current extended price.
|
||||||
*/
|
*/
|
||||||
const handleUpdateHours = (minutes: string) => {
|
const handleUpdateHours = (minutes: string) => {
|
||||||
updateExtendedPriceData(draft => {
|
updateExtendedPriceData(draft => {
|
||||||
draft.duration = parseInt(minutes, 10);
|
draft.duration = parseFloat(minutes) * 60;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -53,10 +53,10 @@ export const ExtendedPriceForm: React.FC<ExtendedPriceFormProps> = ({ formId, on
|
|||||||
<label htmlFor="duration">{t('app.admin.extended_price_form.duration')} *</label>
|
<label htmlFor="duration">{t('app.admin.extended_price_form.duration')} *</label>
|
||||||
<FabInput id="duration"
|
<FabInput id="duration"
|
||||||
type="number"
|
type="number"
|
||||||
defaultValue={extendedPriceData?.duration || ''}
|
defaultValue={extendedPriceData?.duration / 60 || ''}
|
||||||
onChange={handleUpdateHours}
|
onChange={handleUpdateHours}
|
||||||
step={1}
|
step={0.25}
|
||||||
min={1}
|
min={0.5}
|
||||||
icon={<i className="fas fa-clock" />}
|
icon={<i className="fas fa-clock" />}
|
||||||
required />
|
required />
|
||||||
<label htmlFor="amount">{t('app.admin.extended_price_form.amount')} *</label>
|
<label htmlFor="amount">{t('app.admin.extended_price_form.amount')} *</label>
|
||||||
|
@ -9,7 +9,7 @@ interface AvatarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders the user-profile's picture or a placeholder
|
* This component renders the user-profile's picture or a placeholder.
|
||||||
*/
|
*/
|
||||||
export const Avatar: React.FC<AvatarProps> = ({ user, className }) => {
|
export const Avatar: React.FC<AvatarProps> = ({ user, className }) => {
|
||||||
/**
|
/**
|
||||||
|
@ -407,10 +407,30 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
|||||||
// check if slot is not in the past
|
// check if slot is not in the past
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
if (Math.trunc((start.valueOf() - today) / (60 * 1000)) < 0) {
|
if (Math.trunc((start.valueOf() - today) / (60 * 1000)) < 0) {
|
||||||
growl.warning(_t('app.admin.calendar.event_in_the_past'));
|
return dialogs.confirm({
|
||||||
return uiCalendarConfig.calendars.calendar.fullCalendar('unselect');
|
resolve: {
|
||||||
|
object () {
|
||||||
|
return {
|
||||||
|
title: _t('app.admin.calendar.event_in_the_past'),
|
||||||
|
msg: _t('app.admin.calendar.confirm_create_event_in_the_past')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function () { // confirmed
|
||||||
|
startAvailabilityCreation(start, end);
|
||||||
|
}, function () { // canceled
|
||||||
|
uiCalendarConfig.calendars.calendar.fullCalendar('unselect');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startAvailabilityCreation(start, end);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the process to create a new availability between the given start and end datetimes
|
||||||
|
*/
|
||||||
|
const startAvailabilityCreation = function (start, end) {
|
||||||
// check that the selected slot is an multiple of SLOT_MULTIPLE (ie. not decimal)
|
// check that the selected slot is an multiple of SLOT_MULTIPLE (ie. not decimal)
|
||||||
const slots = Math.trunc((end.valueOf() - start.valueOf()) / (60 * 1000)) / SLOT_MULTIPLE;
|
const slots = Math.trunc((end.valueOf() - start.valueOf()) / (60 * 1000)) / SLOT_MULTIPLE;
|
||||||
if (!Number.isInteger(slots)) {
|
if (!Number.isInteger(slots)) {
|
||||||
|
@ -478,6 +478,13 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
|||||||
$scope.isSelected = active;
|
$scope.isSelected = active;
|
||||||
$scope.history = [];
|
$scope.history = [];
|
||||||
|
|
||||||
|
// callback on "enable VAT" switch toggle
|
||||||
|
$scope.enableVATChanged = function (checked) {
|
||||||
|
setTimeout(() => {
|
||||||
|
$scope.isSelected = checked;
|
||||||
|
$scope.$apply();
|
||||||
|
}, 1);
|
||||||
|
};
|
||||||
$scope.ok = function () { $uibModalInstance.close({ rate: $scope.rate, active: $scope.isSelected }); };
|
$scope.ok = function () { $uibModalInstance.close({ rate: $scope.rate, active: $scope.isSelected }); };
|
||||||
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
|
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
|
||||||
$scope.editMultiVAT = function () {
|
$scope.editMultiVAT = function () {
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
Application.Controllers.controller('HeaderController', ['$scope', '$rootScope', '$state',
|
Application.Controllers.controller('HeaderController', ['$scope', '$rootScope', '$state', 'settingsPromise',
|
||||||
function ($scope, $rootScope, $state) {
|
function ($scope, $rootScope, $state, settingsPromise) {
|
||||||
$scope.aboutPage = ($state.current.name === 'app.public.about');
|
$scope.aboutPage = ($state.current.name === 'app.public.about');
|
||||||
|
|
||||||
$rootScope.$on('$stateChangeStart', function (event, toState) {
|
$rootScope.$on('$stateChangeStart', function (event, toState) {
|
||||||
$scope.aboutPage = (toState.name === 'app.public.about');
|
$scope.aboutPage = (toState.name === 'app.public.about');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current state of the public registration setting (allowed/blocked).
|
||||||
|
*/
|
||||||
|
$scope.registrationEnabled = function () {
|
||||||
|
return settingsPromise.public_registrations === 'true';
|
||||||
|
};
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
/**
|
/**
|
||||||
* Navigation controller. List the links availables in the left navigation pane and their icon.
|
* Navigation controller. List the links availables in the left navigation pane and their icon.
|
||||||
*/
|
*/
|
||||||
Application.Controllers.controller('MainNavController', ['$scope', function ($scope) {
|
Application.Controllers.controller('MainNavController', ['$scope', 'settingsPromise', function ($scope, settingsPromise) {
|
||||||
// Common links (public application)
|
// Common links (public application)
|
||||||
$scope.navLinks = [
|
$scope.navLinks = [
|
||||||
{
|
{
|
||||||
@ -172,5 +172,12 @@ Application.Controllers.controller('MainNavController', ['$scope', function ($sc
|
|||||||
authorizedRoles: ['admin']
|
authorizedRoles: ['admin']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current state of the public registration setting (allowed/blocked).
|
||||||
|
*/
|
||||||
|
$scope.registrationEnabled = function () {
|
||||||
|
return settingsPromise.public_registrations === 'true';
|
||||||
|
};
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
@ -34,8 +34,8 @@ Application.Directives.directive('fileread', [() =>
|
|||||||
Application.Directives.directive('bsHolder', [() =>
|
Application.Directives.directive('bsHolder', [() =>
|
||||||
({
|
({
|
||||||
link (scope, element, attrs) {
|
link (scope, element, attrs) {
|
||||||
Holder.addTheme('icon', { background: 'white', foreground: '#e9e9e9', size: 80, font: 'FontAwesome' })
|
Holder.addTheme('icon', { background: 'white', foreground: '#ebebeb', size: 60, font: 'FontAwesome' })
|
||||||
.addTheme('icon-xs', { background: 'white', foreground: '#e0e0e0', size: 20, font: 'FontAwesome' })
|
.addTheme('icon-xs', { background: 'white', foreground: '#ebebeb', size: 20, font: 'FontAwesome' })
|
||||||
.addTheme('icon-black-xs', { background: 'black', foreground: 'white', size: 20, font: 'FontAwesome' })
|
.addTheme('icon-black-xs', { background: 'black', foreground: 'white', size: 20, font: 'FontAwesome' })
|
||||||
.addTheme('avatar', { background: '#eeeeee', foreground: '#555555', size: 16, font: 'FontAwesome' })
|
.addTheme('avatar', { background: '#eeeeee', foreground: '#555555', size: 16, font: 'FontAwesome' })
|
||||||
.run(element[0]);
|
.run(element[0]);
|
||||||
|
@ -4,13 +4,16 @@ export enum PaymentScheduleItemState {
|
|||||||
RequirePaymentMethod = 'requires_payment_method',
|
RequirePaymentMethod = 'requires_payment_method',
|
||||||
RequireAction = 'requires_action',
|
RequireAction = 'requires_action',
|
||||||
Paid = 'paid',
|
Paid = 'paid',
|
||||||
Error = 'error'
|
Error = 'error',
|
||||||
|
GatewayCanceled = 'gateway_canceled'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PaymentMethod {
|
export enum PaymentMethod {
|
||||||
Stripe = 'stripe',
|
Card = 'card',
|
||||||
|
Transfer = 'transfer',
|
||||||
Check = 'check'
|
Check = 'check'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaymentScheduleItem {
|
export interface PaymentScheduleItem {
|
||||||
id: number,
|
id: number,
|
||||||
amount: number,
|
amount: number,
|
||||||
@ -22,30 +25,28 @@ export interface PaymentScheduleItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PaymentSchedule {
|
export interface PaymentSchedule {
|
||||||
max_length: number;
|
max_length?: number;
|
||||||
id: number,
|
id: number,
|
||||||
total: number,
|
total?: number,
|
||||||
reference: string,
|
reference?: string,
|
||||||
payment_method: string,
|
payment_method: PaymentMethod,
|
||||||
items: Array<PaymentScheduleItem>,
|
items?: Array<PaymentScheduleItem>,
|
||||||
created_at: Date,
|
created_at?: Date,
|
||||||
chained_footprint: boolean,
|
chained_footprint?: boolean,
|
||||||
main_object: {
|
main_object?: {
|
||||||
type: string,
|
type: string,
|
||||||
id: number
|
id: number
|
||||||
},
|
},
|
||||||
user: {
|
user?: {
|
||||||
id: number,
|
id: number,
|
||||||
name: string
|
name: string
|
||||||
},
|
},
|
||||||
operator: {
|
operator?: {
|
||||||
id: number,
|
id: number,
|
||||||
first_name: string,
|
first_name: string,
|
||||||
last_name: string,
|
last_name: string,
|
||||||
},
|
},
|
||||||
gateway_subscription: {
|
gateway?: 'PayZen' | 'Stripe',
|
||||||
classname: string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaymentScheduleIndexRequest {
|
export interface PaymentScheduleIndexRequest {
|
||||||
|
@ -18,7 +18,8 @@ export interface IntentConfirmation {
|
|||||||
|
|
||||||
export enum PaymentMethod {
|
export enum PaymentMethod {
|
||||||
Card = 'card',
|
Card = 'card',
|
||||||
Other = ''
|
Check = 'check',
|
||||||
|
Transfer = 'transfer'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartItem = { reservation: Reservation }|
|
export type CartItem = { reservation: Reservation }|
|
||||||
|
@ -117,7 +117,8 @@ export enum SettingName {
|
|||||||
RenewPackThreshold = 'renew_pack_threshold',
|
RenewPackThreshold = 'renew_pack_threshold',
|
||||||
PackOnlyForSubscription = 'pack_only_for_subscription',
|
PackOnlyForSubscription = 'pack_only_for_subscription',
|
||||||
OverlappingCategories = 'overlapping_categories',
|
OverlappingCategories = 'overlapping_categories',
|
||||||
ExtendedPricesInSameDay = 'extended_prices_in_same_day'
|
ExtendedPricesInSameDay = 'extended_prices_in_same_day',
|
||||||
|
PublicRegistrations = 'public_registrations'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SettingValue = string|boolean|number;
|
export type SettingValue = string|boolean|number;
|
||||||
|
@ -38,7 +38,8 @@ angular.module('application.router', ['ui.router'])
|
|||||||
logoFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'logo-file' }).$promise; }],
|
logoFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'logo-file' }).$promise; }],
|
||||||
logoBlackFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'logo-black-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', 'statistics_module', 'trainings_module', 'public_agenda_module']" }).$promise; }]
|
modulesPromise: ['Setting', function (Setting) { return Setting.query({ names: "['spaces_module', 'plans_module', 'invoicing_module', 'wallet_module', 'statistics_module', 'trainings_module', 'public_agenda_module']" }).$promise; }],
|
||||||
|
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['public_registrations']" }).$promise; }]
|
||||||
},
|
},
|
||||||
onEnter: ['$rootScope', 'logoFile', 'logoBlackFile', 'modulesPromise', 'CSRF', function ($rootScope, logoFile, logoBlackFile, modulesPromise, CSRF) {
|
onEnter: ['$rootScope', 'logoFile', 'logoBlackFile', 'modulesPromise', 'CSRF', function ($rootScope, logoFile, logoBlackFile, modulesPromise, CSRF) {
|
||||||
// Retrieve Anti-CSRF tokens from cookies
|
// Retrieve Anti-CSRF tokens from cookies
|
||||||
@ -1081,7 +1082,8 @@ angular.module('application.router', ['ui.router'])
|
|||||||
"'reminder_delay', 'visibility_yearly', 'visibility_others', 'wallet_module', 'trainings_module', " +
|
"'reminder_delay', 'visibility_yearly', 'visibility_others', 'wallet_module', 'trainings_module', " +
|
||||||
"'display_name_enable', 'machines_sort_by', 'fab_analytics', 'statistics_module', 'address_required', " +
|
"'display_name_enable', 'machines_sort_by', 'fab_analytics', 'statistics_module', 'address_required', " +
|
||||||
"'link_name', 'home_content', 'home_css', 'phone_required', 'upcoming_events_shown', 'public_agenda_module'," +
|
"'link_name', 'home_content', 'home_css', 'phone_required', 'upcoming_events_shown', 'public_agenda_module'," +
|
||||||
"'renew_pack_threshold', 'pack_only_for_subscription', 'overlapping_categories', 'extended_prices_in_same_day']"
|
"'renew_pack_threshold', 'pack_only_for_subscription', 'overlapping_categories', 'public_registrations'," +
|
||||||
|
"'extended_prices_in_same_day']"
|
||||||
}).$promise;
|
}).$promise;
|
||||||
}],
|
}],
|
||||||
privacyDraftsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'privacy_draft', history: true }).$promise; }],
|
privacyDraftsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'privacy_draft', history: true }).$promise; }],
|
||||||
|
@ -191,7 +191,9 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-height: 400px;
|
aspect-ratio: 16/9;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,6 +247,8 @@
|
|||||||
height: 250px;
|
height: 250px;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
@include transition(opacity 0.5s ease);
|
@include transition(opacity 0.5s ease);
|
||||||
|
|
||||||
@ -302,6 +306,7 @@
|
|||||||
|
|
||||||
&.avatar-block {
|
&.avatar-block {
|
||||||
white-space: inherit;
|
white-space: inherit;
|
||||||
|
width: 100%;
|
||||||
height: 76px;
|
height: 76px;
|
||||||
|
|
||||||
.user-name {
|
.user-name {
|
||||||
|
@ -661,8 +661,121 @@ body.container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.home-events {
|
.home-events {
|
||||||
.event-description {
|
h4 {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
column-gap: 1rem;
|
||||||
|
i { margin-right: 1rem;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-events-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
|
||||||
|
gap: 30px;
|
||||||
|
margin-bottom: 5rem;
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Event {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
& .Event-picture {opacity: 0.7;}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-picture {
|
||||||
|
height: 250px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #ebebeb;
|
||||||
|
transition: opacity 0.4s ease-out;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-desc {
|
||||||
|
position: relative;
|
||||||
|
padding: 15px;
|
||||||
|
h3 {
|
||||||
|
max-width: 75%;
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 1.3;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #000 !important;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
position: absolute;
|
||||||
|
right: 15px;
|
||||||
|
top: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-info {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: repeat(2, max-content);
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 15px;
|
||||||
|
margin-top: auto;
|
||||||
|
padding: 15px 30px 30px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
&-item {
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
i {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: center;
|
||||||
|
color: #cb1117;
|
||||||
|
}
|
||||||
|
h6 { margin: 0 0 0 15px;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-events-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(440px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.Event {
|
||||||
|
display: flex;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&-desc {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px 15px 15px;
|
||||||
|
}
|
||||||
|
&-picture {
|
||||||
|
width: 33%;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,3 +76,5 @@
|
|||||||
@import "modules/subscriptions/renew-modal";
|
@import "modules/subscriptions/renew-modal";
|
||||||
|
|
||||||
@import "app.responsive";
|
@import "app.responsive";
|
||||||
|
|
||||||
|
@import "overrides"
|
||||||
|
@ -53,26 +53,28 @@
|
|||||||
height: 250px;
|
height: 250px;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
transition: opacity 0.5s ease;
|
transition: opacity 0.5s ease;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
background-color: #f5f5f5;
|
border-bottom: 1px solid #ebebeb;
|
||||||
border-color: #ddd;
|
|
||||||
border-bottom: 1px solid transparent;
|
|
||||||
border-top-left-radius: 5px;
|
border-top-left-radius: 5px;
|
||||||
border-top-right-radius: 5px;
|
border-top-right-radius: 5px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&.no-picture::before {
|
&.no-picture::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
content: '\f03e';
|
content: '\f03e';
|
||||||
font-family: 'Font Awesome 5 Free' !important;
|
font-family: 'Font Awesome 5 Free' !important;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
font-size: 4em;
|
font-size: 80px;
|
||||||
width: 100%;
|
color: #ebebeb;
|
||||||
text-align: center;
|
|
||||||
padding-top: 84px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
26
app/frontend/src/stylesheets/overrides.scss
Normal file
26
app/frontend/src/stylesheets/overrides.scss
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
.carousel {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
content: '\f03e';
|
||||||
|
font-family: 'Font Awesome 5 Free' !important;
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 80px;
|
||||||
|
color: #ebebeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-inner {
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-inner > .item {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||||
|
@ -64,10 +64,11 @@
|
|||||||
</td>
|
</td>
|
||||||
<td ng-class="{'has-error': mappingForm['auth_mapping[api_field]'].$dirty && mappingForm['auth_mapping[api_field]'].$invalid}">
|
<td ng-class="{'has-error': mappingForm['auth_mapping[api_field]'].$dirty && mappingForm['auth_mapping[api_field]'].$invalid}">
|
||||||
<input type="text"
|
<input type="text"
|
||||||
class="form-control"
|
class="form-control help-cursor"
|
||||||
placeholder="field_name"
|
placeholder="field_name"
|
||||||
ng-model="newMapping.api_field"
|
ng-model="newMapping.api_field"
|
||||||
name="auth_mapping[api_field]"
|
name="auth_mapping[api_field]"
|
||||||
|
title="{{ 'app.shared.oauth2.api_field_help' | translate }}"
|
||||||
required/>
|
required/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8 b-l b-r-md">
|
<div class="col-md-8 b-l b-r-md">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter b-b">
|
<div class="row no-gutter b-b">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter b-b">
|
<div class="row no-gutter b-b">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r">
|
||||||
|
@ -37,13 +37,13 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:8%"></th>
|
<th style="width:8%"></th>
|
||||||
<th style="width:14%"><a href="" ng-click="setOrderInvoice('reference')">{{ 'app.admin.invoices.invoice_num' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='reference', 'fa fa-sort-numeric-desc': orderInvoice=='-reference', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
|
<th style="width:14%"><a ng-click="setOrderInvoice('reference')">{{ 'app.admin.invoices.invoice_num' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='reference', 'fa fa-sort-numeric-desc': orderInvoice=='-reference', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
|
||||||
|
|
||||||
<th style="width:19%"><a href="" ng-click="setOrderInvoice('date')">{{ 'app.admin.invoices.date' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='date', 'fa fa-sort-numeric-desc': orderInvoice=='-date', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
|
<th style="width:19%"><a ng-click="setOrderInvoice('date')">{{ 'app.admin.invoices.date' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='date', 'fa fa-sort-numeric-desc': orderInvoice=='-date', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
|
||||||
|
|
||||||
<th style="width:9%"><a href="" ng-click="setOrderInvoice('total')"> {{ 'app.admin.invoices.price' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='total', 'fa fa-sort-numeric-desc': orderInvoice=='-total', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
|
<th style="width:9%"><a ng-click="setOrderInvoice('total')"> {{ 'app.admin.invoices.price' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='total', 'fa fa-sort-numeric-desc': orderInvoice=='-total', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
|
||||||
|
|
||||||
<th style="width:20%"><a href="" ng-click="setOrderInvoice('name')">{{ 'app.admin.invoices.customer' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderInvoice=='name', 'fa fa-sort-alpha-desc': orderInvoice=='-name', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
|
<th style="width:20%"><a ng-click="setOrderInvoice('name')">{{ 'app.admin.invoices.customer' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderInvoice=='name', 'fa fa-sort-alpha-desc': orderInvoice=='-name', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
|
||||||
|
|
||||||
<th style="width:30%"></th>
|
<th style="width:30%"></th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -64,7 +64,7 @@
|
|||||||
<td ng-if="invoice.is_avoir">{{ invoice.date | amDateFormat:'L' }}</td>
|
<td ng-if="invoice.is_avoir">{{ invoice.date | amDateFormat:'L' }}</td>
|
||||||
<td>{{ invoice.total | currency}}</td>
|
<td>{{ invoice.total | currency}}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="" ui-sref="app.admin.members_edit({id: invoice.user_id})" ng-show="invoice.user_id">{{ invoice.name }}</a>
|
<a ui-sref="app.admin.members_edit({id: invoice.user_id})" ng-show="invoice.user_id">{{ invoice.name }}</a>
|
||||||
<span ng-hide="invoice.user_id">{{ invoice.name }}</span>
|
<span ng-hide="invoice.user_id">{{ invoice.name }}</span>
|
||||||
<td>
|
<td>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
@ -5,14 +5,11 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="enableVAT" class="control-label" translate>{{ 'app.admin.invoices.enable_VAT' }}</label>
|
<label for="enableVAT" class="control-label" translate>{{ 'app.admin.invoices.enable_VAT' }}</label>
|
||||||
<input bs-switch
|
<switch id="enableVAT"
|
||||||
ng-model="isSelected"
|
checked="isSelected"
|
||||||
id="enableVAT"
|
on-change="enableVATChanged"
|
||||||
type="checkbox"
|
classname="form-control m-l-sm">
|
||||||
class="form-control m-l-sm"
|
</switch>
|
||||||
switch-on-text="{{ 'app.admin.invoices.enabled' | translate }}"
|
|
||||||
switch-off-text="{{ 'app.admin.invoices.disabled' | translate }}"
|
|
||||||
switch-animate="true"/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group" ng-show="isSelected">
|
<div class="form-group" ng-show="isSelected">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||||
|
@ -14,13 +14,13 @@
|
|||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:15%"><a href="" ng-click="setOrderAdmin('profile_attributes.last_name')">{{ 'app.admin.members.surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='profile_attributes.last_name', 'fa fa-sort-alpha-desc': orderAdmin =='-profile_attributes.last_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
<th style="width:15%"><a ng-click="setOrderAdmin('profile_attributes.last_name')">{{ 'app.admin.members.surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='profile_attributes.last_name', 'fa fa-sort-alpha-desc': orderAdmin =='-profile_attributes.last_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||||
|
|
||||||
<th style="width:15%"><a href="" ng-click="setOrderAdmin('profile_attributes.first_name')">{{ 'app.admin.members.first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='profile_attributes.first_name', 'fa fa-sort-alpha-desc': orderAdmin =='-profile_attributes.first_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
<th style="width:15%"><a ng-click="setOrderAdmin('profile_attributes.first_name')">{{ 'app.admin.members.first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='profile_attributes.first_name', 'fa fa-sort-alpha-desc': orderAdmin =='-profile_attributes.first_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||||
|
|
||||||
<th style="width:15%"><a href="" ng-click="setOrderAdmin('email')">{{ 'app.admin.members.email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='email', 'fa fa-sort-alpha-desc': orderAdmin =='-email', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
<th style="width:15%"><a ng-click="setOrderAdmin('email')">{{ 'app.admin.members.email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='email', 'fa fa-sort-alpha-desc': orderAdmin =='-email', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||||
|
|
||||||
<th style="width:10%"><a href="" ng-click="setOrderAdmin('profile_attributes.phone')">{{ 'app.admin.members.phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderAdmin =='profile_attributes.phone', 'fa fa-sort-numeric-desc': orderAdmin =='-profile_attributes.phone', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
<th style="width:10%"><a ng-click="setOrderAdmin('profile_attributes.phone')">{{ 'app.admin.members.phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderAdmin =='profile_attributes.phone', 'fa fa-sort-numeric-desc': orderAdmin =='-profile_attributes.phone', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||||
<th style="width:10%"></th>
|
<th style="width:10%"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-8 col-sm-8 col-md-8 b-l">
|
<div class="col-xs-8 col-sm-8 col-md-8 b-l">
|
||||||
|
@ -18,13 +18,13 @@
|
|||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:15%"><a href="" ng-click="setOrderManager('last_name')">{{ 'app.admin.members.surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='last_name', 'fa fa-sort-alpha-desc': orderAdmin =='-last_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
<th style="width:15%"><a ng-click="setOrderManager('last_name')">{{ 'app.admin.members.surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='last_name', 'fa fa-sort-alpha-desc': orderAdmin =='-last_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||||
|
|
||||||
<th style="width:15%"><a href="" ng-click="setOrderManager('first_name')">{{ 'app.admin.members.first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='first_name', 'fa fa-sort-alpha-desc': orderAdmin =='-first_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
<th style="width:15%"><a ng-click="setOrderManager('first_name')">{{ 'app.admin.members.first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='first_name', 'fa fa-sort-alpha-desc': orderAdmin =='-first_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||||
|
|
||||||
<th style="width:15%"><a href="" ng-click="setOrderManager('email')">{{ 'app.admin.members.email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='email', 'fa fa-sort-alpha-desc': orderAdmin =='-email', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
<th style="width:15%"><a ng-click="setOrderManager('email')">{{ 'app.admin.members.email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='email', 'fa fa-sort-alpha-desc': orderAdmin =='-email', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||||
|
|
||||||
<th style="width:10%"><a href="" ng-click="setOrderManager('phone')">{{ 'app.admin.members.phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderAdmin =='profile_attributes.phone', 'fa fa-sort-numeric-desc': orderAdmin =='-profile_attributes.phone', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
<th style="width:10%"><a ng-click="setOrderManager('phone')">{{ 'app.admin.members.phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderAdmin =='profile_attributes.phone', 'fa fa-sort-numeric-desc': orderAdmin =='-profile_attributes.phone', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||||
<th style="width:10%"></th>
|
<th style="width:10%"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -37,12 +37,12 @@
|
|||||||
<table class="table members-list">
|
<table class="table members-list">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:15%"><a href="" ng-click="setOrderMember('last_name')">{{ 'app.admin.members.surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='last_name', 'fa fa-sort-alpha-desc': member.order=='-last_name', 'fa fa-arrows-v': member.order }"></i></a></th>
|
<th style="width:15%"><a ng-click="setOrderMember('last_name')">{{ 'app.admin.members.surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='last_name', 'fa fa-sort-alpha-desc': member.order=='-last_name', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||||
<th style="width:15%"><a href="" ng-click="setOrderMember('first_name')">{{ 'app.admin.members.first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='first_name', 'fa fa-sort-alpha-desc': member.order=='-first_name', 'fa fa-arrows-v': member.order }"></i></a></th>
|
<th style="width:15%"><a ng-click="setOrderMember('first_name')">{{ 'app.admin.members.first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='first_name', 'fa fa-sort-alpha-desc': member.order=='-first_name', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||||
<th style="width:15%" class="hidden-xs"><a href="" ng-click="setOrderMember('email')">{{ 'app.admin.members.email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='email', 'fa fa-sort-alpha-desc': member.order=='-email', 'fa fa-arrows-v': member.order }"></i></a></th>
|
<th style="width:15%" class="hidden-xs"><a ng-click="setOrderMember('email')">{{ 'app.admin.members.email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='email', 'fa fa-sort-alpha-desc': member.order=='-email', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||||
<th style="width:10%" class="hidden-xs hidden-sm hidden-md"><a href="" ng-click="setOrderMember('phone')">{{ 'app.admin.members.phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': member.order=='phone', 'fa fa-sort-numeric-desc': member.order=='-phone', 'fa fa-arrows-v': member.order }"></i></a></th>
|
<th style="width:10%" class="hidden-xs hidden-sm hidden-md"><a ng-click="setOrderMember('phone')">{{ 'app.admin.members.phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': member.order=='phone', 'fa fa-sort-numeric-desc': member.order=='-phone', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||||
<th style="width:15%" class="hidden-xs hidden-sm"><a href="" ng-click="setOrderMember('group')">{{ 'app.admin.members.user_type' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='group', 'fa fa-sort-alpha-desc': member.order=='-group', 'fa fa-arrows-v': member.order }"></i></a></th>
|
<th style="width:15%" class="hidden-xs hidden-sm"><a ng-click="setOrderMember('group')">{{ 'app.admin.members.user_type' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='group', 'fa fa-sort-alpha-desc': member.order=='-group', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||||
<th style="width:15%" class="hidden-xs hidden-sm hidden-md"><a href="" ng-click="setOrderMember('plan')">{{ 'app.admin.members.subscription' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='plan', 'fa fa-sort-alpha-desc': member.order=='-plan', 'fa fa-arrows-v': member.order }"></i></a></th>
|
<th style="width:15%" class="hidden-xs hidden-sm hidden-md"><a ng-click="setOrderMember('plan')">{{ 'app.admin.members.subscription' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='plan', 'fa fa-sort-alpha-desc': member.order=='-plan', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||||
<th style="width:15%" class="buttons-col"></th>
|
<th style="width:15%" class="buttons-col"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -16,13 +16,13 @@
|
|||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:15%"><a href="" ng-click="setOrderPartner('last_name')">{{ 'app.admin.members.surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPartner =='last_name', 'fa fa-sort-alpha-desc': orderPartner =='-last_name', 'fa fa-arrows-v': orderPartner }"></i></a></th>
|
<th style="width:15%"><a ng-click="setOrderPartner('last_name')">{{ 'app.admin.members.surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPartner =='last_name', 'fa fa-sort-alpha-desc': orderPartner =='-last_name', 'fa fa-arrows-v': orderPartner }"></i></a></th>
|
||||||
|
|
||||||
<th style="width:15%"><a href="" ng-click="setOrderPartner('first_name')">{{ 'app.admin.members.first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPartner =='first_name', 'fa fa-sort-alpha-desc': orderPartner =='-first_name', 'fa fa-arrows-v': orderPartner }"></i></a></th>
|
<th style="width:15%"><a ng-click="setOrderPartner('first_name')">{{ 'app.admin.members.first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPartner =='first_name', 'fa fa-sort-alpha-desc': orderPartner =='-first_name', 'fa fa-arrows-v': orderPartner }"></i></a></th>
|
||||||
|
|
||||||
<th style="width:15%"><a href="" ng-click="setOrderPartner('email')">{{ 'app.admin.members.email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPartner =='email', 'fa fa-sort-alpha-desc': orderPartner =='-email', 'fa fa-arrows-v': orderPartner }"></i></a></th>
|
<th style="width:15%"><a ng-click="setOrderPartner('email')">{{ 'app.admin.members.email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPartner =='email', 'fa fa-sort-alpha-desc': orderPartner =='-email', 'fa fa-arrows-v': orderPartner }"></i></a></th>
|
||||||
|
|
||||||
<th style="width:10%"><a href="" ng-click="setOrderPartner('resource')">{{ 'app.admin.members.associated_plan' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderPartner =='resource', 'fa fa-sort-numeric-desc': orderPartner =='-resource', 'fa fa-arrows-v': orderPartner }"></i></a></th>
|
<th style="width:10%"><a ng-click="setOrderPartner('resource')">{{ 'app.admin.members.associated_plan' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderPartner =='resource', 'fa fa-sort-numeric-desc': orderPartner =='-resource', 'fa fa-arrows-v': orderPartner }"></i></a></th>
|
||||||
<th style="width:10%"></th>
|
<th style="width:10%"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-7 b-l b-r-md">
|
<div class="col-xs-10 col-sm-10 col-md-7 b-l b-r-md">
|
||||||
@ -50,13 +50,13 @@
|
|||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:20%"><a href="" ng-click="setOrder('name')">{{ 'app.admin.open_api_clients.name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': order == 'name', 'fa fa-sort-alpha-desc': order == '-name', 'fa fa-arrows-v': order }"></i></a></th>
|
<th style="width:20%"><a ng-click="setOrder('name')">{{ 'app.admin.open_api_clients.name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': order == 'name', 'fa fa-sort-alpha-desc': order == '-name', 'fa fa-arrows-v': order }"></i></a></th>
|
||||||
|
|
||||||
<th style="width:15%"><a href="" ng-click="setOrder('calls_count')">{{ 'app.admin.open_api_clients.calls_count' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': order == 'calls_count', 'fa fa-sort-numeric-desc': order == '-calls_count', 'fa fa-arrows-v': order }"></i></a></th>
|
<th style="width:15%"><a ng-click="setOrder('calls_count')">{{ 'app.admin.open_api_clients.calls_count' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': order == 'calls_count', 'fa fa-sort-numeric-desc': order == '-calls_count', 'fa fa-arrows-v': order }"></i></a></th>
|
||||||
|
|
||||||
<th style="width:20%"><a href="">{{ 'app.admin.open_api_clients.token' | translate }}</a></th>
|
<th style="width:20%"><a>{{ 'app.admin.open_api_clients.token' | translate }}</a></th>
|
||||||
|
|
||||||
<th style="width:20%"><a href="" ng-click="setOrder('created_at')">{{ 'app.admin.open_api_clients.created_at' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': order == 'created_at', 'fa fa-sort-numeric-desc': order == '-created_at', 'fa fa-arrows-v': order }"></i></a></th>
|
<th style="width:20%"><a ng-click="setOrder('created_at')">{{ 'app.admin.open_api_clients.created_at' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': order == 'created_at', 'fa fa-sort-numeric-desc': order == '-created_at', 'fa fa-arrows-v': order }"></i></a></th>
|
||||||
|
|
||||||
<th style="width:25%"></th>
|
<th style="width:25%"></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r">
|
||||||
|
@ -23,12 +23,12 @@
|
|||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><a href="" ng-click="setOrderPlans('name')">{{ 'app.admin.pricing.name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPlans=='name', 'fa fa-sort-alpha-desc': orderPlans=='-name', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
<th><a ng-click="setOrderPlans('name')">{{ 'app.admin.pricing.name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPlans=='name', 'fa fa-sort-alpha-desc': orderPlans=='-name', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||||
<th><a href="" ng-click="setOrderPlans('interval')">{{ 'app.admin.pricing.duration' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-amount-asc': orderPlans=='interval', 'fa fa-sort-amount-desc': orderPlans=='-interval', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
<th><a ng-click="setOrderPlans('interval')">{{ 'app.admin.pricing.duration' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-amount-asc': orderPlans=='interval', 'fa fa-sort-amount-desc': orderPlans=='-interval', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||||
<th><a href="" ng-click="setOrderPlans('group_id')">{{ 'app.admin.pricing.group' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPlans=='group_id', 'fa fa-sort-alpha-desc': orderPlans=='-group_id', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
<th><a ng-click="setOrderPlans('group_id')">{{ 'app.admin.pricing.group' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPlans=='group_id', 'fa fa-sort-alpha-desc': orderPlans=='-group_id', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||||
<th><a href="" ng-click="setOrderPlans('plan_category_id')">{{ 'app.admin.pricing.category' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPlans=='plan_category_id', 'fa fa-sort-alpha-desc': orderPlans=='-plan_category_id', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
<th><a ng-click="setOrderPlans('plan_category_id')">{{ 'app.admin.pricing.category' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPlans=='plan_category_id', 'fa fa-sort-alpha-desc': orderPlans=='-plan_category_id', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||||
<th class="hidden-xs"><a href="" ng-click="setOrderPlans('app.admin.pricing.ui_weight')">{{ 'app.admin.pricing.prominence' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderPlans=='ui_weight', 'fa fa-sort-numeric-desc': orderPlans=='-ui_weight', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
<th class="hidden-xs"><a ng-click="setOrderPlans('app.admin.pricing.ui_weight')">{{ 'app.admin.pricing.prominence' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderPlans=='ui_weight', 'fa fa-sort-numeric-desc': orderPlans=='-ui_weight', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||||
<th><a href="" ng-click="setOrderPlans('amount')">{{ 'app.admin.pricing.price' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderPlans=='amount', 'fa fa-sort-numeric-desc': orderPlans=='-amount', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
<th><a ng-click="setOrderPlans('amount')">{{ 'app.admin.pricing.price' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderPlans=='amount', 'fa fa-sort-numeric-desc': orderPlans=='-amount', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||||
|
@ -202,7 +202,7 @@
|
|||||||
<input name="_method" type="hidden" ng-value="methods.logo">
|
<input name="_method" type="hidden" ng-value="methods.logo">
|
||||||
<h3 class="m-l" translate>{{ 'app.admin.settings.logo_white_background' }}</h3>
|
<h3 class="m-l" translate>{{ 'app.admin.settings.logo_white_background' }}</h3>
|
||||||
<div class="custom-logo" style="background-image: url({{customLogo}});">
|
<div class="custom-logo" style="background-image: url({{customLogo}});">
|
||||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:'Font Awesome 5 Free'/icon-xs" bs-holder ng-show="!customLogo" class="img-responsive">
|
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:'Font Awesome 5 Free'/icon" bs-holder ng-show="!customLogo" class="img-responsive">
|
||||||
<img base-sixty-four-image="customLogo" ng-show="customLogo && customLogo.base64">
|
<img base-sixty-four-image="customLogo" ng-show="customLogo && customLogo.base64">
|
||||||
<img ng-src="{{customLogo.custom_asset_file_attributes.attachment_url}}" alt="{{customLogo.custom_asset_file_attributes.attachment}}" ng-show="customLogo && customLogo.custom_asset_file_attributes" />
|
<img ng-src="{{customLogo.custom_asset_file_attributes.attachment_url}}" alt="{{customLogo.custom_asset_file_attributes.attachment}}" ng-show="customLogo && customLogo.custom_asset_file_attributes" />
|
||||||
<div class="tools-box">
|
<div class="tools-box">
|
||||||
@ -254,7 +254,7 @@
|
|||||||
<input name="_method" type="hidden" ng-value="methods.favicon">
|
<input name="_method" type="hidden" ng-value="methods.favicon">
|
||||||
<h3 class="m-l" translate>{{ 'app.admin.settings.favicon' }}</h3>
|
<h3 class="m-l" translate>{{ 'app.admin.settings.favicon' }}</h3>
|
||||||
<div class="custom-favicon" style="background-image: url({{customFavicon}});">
|
<div class="custom-favicon" style="background-image: url({{customFavicon}});">
|
||||||
<img src="data:image/png;base64," data-src="holder.js/32x32/text:/font:'Font Awesome 5 Free'/icon-xs" bs-holder ng-show="!customFavicon" class="img-responsive">
|
<img src="data:image/png;base64," data-src="holder.js/32x32/text:/font:'Font Awesome 5 Free'/icon" bs-holder ng-show="!customFavicon" class="img-responsive">
|
||||||
<img base-sixty-four-image="customFavicon" ng-show="customFavicon && customFavicon.base64">
|
<img base-sixty-four-image="customFavicon" ng-show="customFavicon && customFavicon.base64">
|
||||||
<img ng-src="{{customFavicon.custom_asset_file_attributes.attachment_url}}" alt="{{customFavicon.custom_asset_file_attributes.attachment}}" ng-show="customFavicon && customFavicon.custom_asset_file_attributes" />
|
<img ng-src="{{customFavicon.custom_asset_file_attributes.attachment_url}}" alt="{{customFavicon.custom_asset_file_attributes.attachment}}" ng-show="customFavicon && customFavicon.custom_asset_file_attributes" />
|
||||||
<div class="tools-box">
|
<div class="tools-box">
|
||||||
@ -316,7 +316,7 @@
|
|||||||
<input name="_method" type="hidden" ng-value="methods.profileImage">
|
<input name="_method" type="hidden" ng-value="methods.profileImage">
|
||||||
<h3 class="m-l" translate>{{ 'app.admin.settings.background_picture_of_the_profile_banner' }}</h3>
|
<h3 class="m-l" translate>{{ 'app.admin.settings.background_picture_of_the_profile_banner' }}</h3>
|
||||||
<div class="custom-profile-image" style="background-image: url({{profileImage}});">
|
<div class="custom-profile-image" style="background-image: url({{profileImage}});">
|
||||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:'Font Awesome 5 Free'/icon-xs" bs-holder ng-show="!profileImage" class="img-responsive">
|
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:'Font Awesome 5 Free'/icon" bs-holder ng-show="!profileImage" class="img-responsive">
|
||||||
<img base-sixty-four-image="profileImage" ng-show="profileImage && profileImage.base64">
|
<img base-sixty-four-image="profileImage" ng-show="profileImage && profileImage.base64">
|
||||||
<img ng-src="{{profileImage.custom_asset_file_attributes.attachment_url}}" alt="{{profileImage.custom_asset_file_attributes.attachment}}" ng-show="profileImage && profileImage.custom_asset_file_attributes" />
|
<img ng-src="{{profileImage.custom_asset_file_attributes.attachment_url}}" alt="{{profileImage.custom_asset_file_attributes.attachment}}" ng-show="profileImage && profileImage.custom_asset_file_attributes" />
|
||||||
<div class="tools-box">
|
<div class="tools-box">
|
||||||
@ -412,6 +412,18 @@
|
|||||||
<span class="font-sbold" translate>{{ 'app.admin.settings.account_creation' }}</span>
|
<span class="font-sbold" translate>{{ 'app.admin.settings.account_creation' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
|
<div class="row">
|
||||||
|
<h3 class="m-l" translate>{{ 'app.admin.settings.general.public_registrations' }}</h3>
|
||||||
|
<p class="alert alert-warning m-h-md" translate>
|
||||||
|
{{ 'app.admin.settings.general.public_registrations_info' }}
|
||||||
|
</p>
|
||||||
|
<div class="col-md-10 col-md-offset-1">
|
||||||
|
<boolean-setting name="public_registrations"
|
||||||
|
settings="allSettings"
|
||||||
|
label="app.admin.settings.general.public_registrations_allowed">
|
||||||
|
</boolean-setting>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h3 class="m-l" translate>{{ 'app.admin.settings.phone' }}</h3>
|
<h3 class="m-l" translate>{{ 'app.admin.settings.phone' }}</h3>
|
||||||
<p class="alert alert-warning m-h-md" translate>
|
<p class="alert alert-warning m-h-md" translate>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
settings="allSettings"
|
settings="allSettings"
|
||||||
label="app.admin.settings.prior_period_hours"
|
label="app.admin.settings.prior_period_hours"
|
||||||
classes="col-md-8"
|
classes="col-md-8"
|
||||||
fa-icon="fa-clock-o"
|
fa-icon="fas fa-clock"
|
||||||
min="0"
|
min="0"
|
||||||
required="allSettings.booking_move_enable === 'true'">
|
required="allSettings.booking_move_enable === 'true'">
|
||||||
</number-setting>
|
</number-setting>
|
||||||
@ -76,7 +76,7 @@
|
|||||||
settings="allSettings"
|
settings="allSettings"
|
||||||
label="app.admin.settings.prior_period_hours"
|
label="app.admin.settings.prior_period_hours"
|
||||||
classes="col-md-8"
|
classes="col-md-8"
|
||||||
fa-icon="fa-clock-o"
|
fa-icon="fas fa-clock"
|
||||||
min="0"
|
min="0"
|
||||||
required="allSettings.booking_cancel_enable === 'true'">
|
required="allSettings.booking_cancel_enable === 'true'">
|
||||||
</number-setting>
|
</number-setting>
|
||||||
@ -112,7 +112,7 @@
|
|||||||
settings="allSettings"
|
settings="allSettings"
|
||||||
label="app.admin.settings.duration_minutes"
|
label="app.admin.settings.duration_minutes"
|
||||||
classes="col-md-4"
|
classes="col-md-4"
|
||||||
fa-icon="fa-clock-o"
|
fa-icon="fas fa-clock"
|
||||||
min="1"
|
min="1"
|
||||||
required="true">
|
required="true">
|
||||||
</number-setting>
|
</number-setting>
|
||||||
@ -161,7 +161,7 @@
|
|||||||
settings="allSettings"
|
settings="allSettings"
|
||||||
label="app.admin.settings.prior_period_hours"
|
label="app.admin.settings.prior_period_hours"
|
||||||
classes="col-md-4"
|
classes="col-md-4"
|
||||||
fa-icon="fa-clock-o"
|
fa-icon="fas fa-clock"
|
||||||
helper-text="app.admin.settings.default_value_is_24_hours"
|
helper-text="app.admin.settings.default_value_is_24_hours"
|
||||||
min="0">
|
min="0">
|
||||||
</number-setting>
|
</number-setting>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-8 col-sm-10 col-md-8 b-l">
|
<div class="col-xs-8 col-sm-10 col-md-8 b-l">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-8 col-sm-11 col-md-8 b-l">
|
<div class="col-xs-8 col-sm-11 col-md-8 b-l">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-md-1 hidden-xs">
|
<div class="col-md-1 hidden-xs">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="cancel()"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="cancel()"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8 b-l b-r">
|
<div class="col-md-8 b-l b-r">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md hide-b-r-lg">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md hide-b-r-lg">
|
||||||
|
@ -3,21 +3,21 @@
|
|||||||
|
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-10 b-l">
|
<div class="col-xs-10 col-sm-10 col-md-10 b-l">
|
||||||
<section class="heading-title m-l">
|
<section class="heading-title m-l">
|
||||||
<h4 class="m-l text-sm" translate>{{ 'app.public.common.dashboard' }}</h4>
|
<h4 class="m-l text-sm" translate>{{ 'app.public.common.dashboard' }}</h4>
|
||||||
<ul class="nav-page nav nav-pills text-u-c text-sm">
|
<ul class="nav-page nav nav-pills text-u-c text-sm">
|
||||||
<li ui-sref-active="active"><a class="text-black" href="#" ui-sref="app.logged.dashboard.profile" translate>{{ 'app.public.common.my_profile' }}</a></li>
|
<li ui-sref-active="active"><a class="text-black" ui-sref="app.logged.dashboard.profile" translate>{{ 'app.public.common.my_profile' }}</a></li>
|
||||||
<li ui-sref-active="active"><a class="text-black" href="#" ui-sref="app.logged.dashboard.settings" translate>{{ 'app.public.common.my_settings' }}</a></li>
|
<li ui-sref-active="active"><a class="text-black" ui-sref="app.logged.dashboard.settings" translate>{{ 'app.public.common.my_settings' }}</a></li>
|
||||||
<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" 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" 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"><a class="text-black" ui-sref="app.logged.dashboard.events" translate>{{ 'app.public.common.my_events' }}</a></li>
|
||||||
<li ui-sref-active="active" ng-show="$root.modules.invoicing"><a class="text-black" href="#" ui-sref="app.logged.dashboard.invoices" translate>{{ 'app.public.common.my_invoices' }}</a></li>
|
<li ui-sref-active="active" ng-show="$root.modules.invoicing"><a class="text-black" ui-sref="app.logged.dashboard.invoices" translate>{{ 'app.public.common.my_invoices' }}</a></li>
|
||||||
<li ui-sref-active="active" ng-show="$root.modules.invoicing"><a class="text-black" href="#" ui-sref="app.logged.dashboard.payment_schedules" translate>{{ 'app.public.common.my_payment_schedules' }}</a></li>
|
<li ui-sref-active="active" ng-show="$root.modules.invoicing"><a class="text-black" ui-sref="app.logged.dashboard.payment_schedules" translate>{{ 'app.public.common.my_payment_schedules' }}</a></li>
|
||||||
<li ng-show="$root.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>
|
<li ng-show="$root.modules.wallet" ui-sref-active="active"><a class="text-black" ui-sref="app.logged.dashboard.wallet" translate>{{ 'app.public.common.my_wallet' }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
@ -257,7 +257,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-1">
|
<div class="col-sm-1">
|
||||||
<input type="hidden" name="event[event_price_categories_attributes][][_destroy]" ng-value="price._destroy">
|
<input type="hidden" name="event[event_price_categories_attributes][][_destroy]" ng-value="price._destroy">
|
||||||
<a class="btn p-h-0" ng-click="removePrice(price, $event)" href="#"><i class="fa fa-times text-danger"></i></a>
|
<a class="btn p-h-0" ng-click="removePrice(price, $event)"><i class="fa fa-times text-danger"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="link-icon m-b" ng-hide="event.prices.length == priceCategories.length">
|
<div class="link-icon m-b" ng-hide="event.prices.length == priceCategories.length">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter b-b">
|
<div class="row no-gutter b-b">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||||
@ -43,14 +43,10 @@
|
|||||||
<div ng-repeat="month in monthOrder">
|
<div ng-repeat="month in monthOrder">
|
||||||
<h1>{{monthNames[month.split(',')[0] - 1]}}, {{month.split(',')[1]}}</h1>
|
<h1>{{monthNames[month.split(',')[0] - 1]}}, {{month.split(',')[1]}}</h1>
|
||||||
|
|
||||||
<div class="row" ng-repeat="event in (eventsGroupByMonth[month].length/3 | array)">
|
<div class="month-events-list" ng-repeat="event in (eventsGroupByMonth[month].length/3 | array)">
|
||||||
|
<a class="Event" ng-repeat="event in eventsGroupByMonth[month].slice(3*$index, 3*$index + 3)" ui-sref="app.public.events_show({id: event.id})">
|
||||||
<div class="col-xs-12 col-sm-6 col-md-4" ng-repeat="event in eventsGroupByMonth[month].slice(3*$index, 3*$index + 3)" ng-click="showEvent(event)">
|
<div class="Event-desc">
|
||||||
|
<h5 class="text-xs m-t-n">{{event.category.name}}</h5>
|
||||||
|
|
||||||
<a class="block bg-white img-full p-sm p-l-m box-h-m event b b-light-dark m-t-sm" ui-sref="app.public.events_show({id: event.id})">
|
|
||||||
<div class="pull-left half-w m-t-n-sm">
|
|
||||||
<h5 class="text-xs">{{event.category.name}}</h5>
|
|
||||||
<h4 class="m-n text-sm clear l-n">{{event.title}}</h4>
|
<h4 class="m-n text-sm clear l-n">{{event.title}}</h4>
|
||||||
<h3 class="m-n" ng-show="onSingleDay(event)">{{event.start_date | amDateFormat:'L'}}</h3>
|
<h3 class="m-n" ng-show="onSingleDay(event)">{{event.start_date | amDateFormat:'L'}}</h3>
|
||||||
<h3 class="m-n" ng-hide="onSingleDay(event)">{{event.start_date | amDateFormat:'L'}} <span class="text-sm font-thin" translate> {{ 'app.public.events_list.to_date' }} </span> {{event.end_date | amDateFormat:'L'}}</h3>
|
<h3 class="m-n" ng-hide="onSingleDay(event)">{{event.start_date | amDateFormat:'L'}} <span class="text-sm font-thin" translate> {{ 'app.public.events_list.to_date' }} </span> {{event.end_date | amDateFormat:'L'}}</h3>
|
||||||
@ -74,14 +70,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Event Image -->
|
<!-- Event Image -->
|
||||||
<div class="pull-right crop-155">
|
<div class="Event-picture" ng-if="event.event_image">
|
||||||
<img class="pull-right" ng-src="{{event.event_image_small}}" title="{{event.title}}" ng-if="event.event_image">
|
<img ng-src="{{event.event_image_small}}" title="{{event.title}}">
|
||||||
<img class="pull-right img-responsive" src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:'Font Awesome 5 Free'/icon" bs-holder ng-if="!event.event_image">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||||
@ -83,7 +83,7 @@
|
|||||||
<dl class="text-sm">
|
<dl class="text-sm">
|
||||||
<dt><i class="fa fa-calendar" aria-hidden="true"></i> {{ 'app.public.events_show.dates' | translate }}</dt>
|
<dt><i class="fa fa-calendar" aria-hidden="true"></i> {{ 'app.public.events_show.dates' | translate }}</dt>
|
||||||
<dd>{{ 'app.public.events_show.beginning' | translate }} <span class="text-u-l">{{event.start_date | amDateFormat:'L'}}</span><br>{{ 'app.public.events_show.ending' | translate }} <span class="text-u-l">{{event.end_date | amDateFormat:'L'}}</span></dd>
|
<dd>{{ 'app.public.events_show.beginning' | translate }} <span class="text-u-l">{{event.start_date | amDateFormat:'L'}}</span><br>{{ 'app.public.events_show.ending' | translate }} <span class="text-u-l">{{event.end_date | amDateFormat:'L'}}</span></dd>
|
||||||
<dt><i class="fa fa-clock-o"></i> {{ 'app.public.events_show.opening_hours' | translate }}</dt>
|
<dt><i class="fas fa-clock"></i> {{ 'app.public.events_show.opening_hours' | translate }}</dt>
|
||||||
<dd ng-if="event.all_day == 'true'"><span translate>{{ 'app.public.events_show.all_day' }}</span></dd>
|
<dd ng-if="event.all_day == 'true'"><span translate>{{ 'app.public.events_show.all_day' }}</span></dd>
|
||||||
<dd ng-if="event.all_day == 'false'">{{ 'app.public.events_show.from_time' | translate }} <span class="text-u-l">{{event.start_date | amDateFormat:'LT'}}</span> {{ 'app.public.events_show.to_time' | translate }} <span class="text-u-l">{{event.end_date | amDateFormat:'LT'}}</span></dd>
|
<dd ng-if="event.all_day == 'false'">{{ 'app.public.events_show.from_time' | translate }} <span class="text-u-l">{{event.start_date | amDateFormat:'LT'}}</span> {{ 'app.public.events_show.to_time' | translate }} <span class="text-u-l">{{event.end_date | amDateFormat:'LT'}}</span></dd>
|
||||||
</dl>
|
</dl>
|
||||||
@ -147,7 +147,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clear">
|
<div class="clear">
|
||||||
<a class="pull-right m-t-xs text-u-l ng-scope" href="#" ng-click="cancelReserve($event)" ng-show="reserve.toReserve" translate>{{ 'app.shared.buttons.cancel' }}</a>
|
<a class="pull-right m-t-xs text-u-l ng-scope" ng-click="cancelReserve($event)" ng-show="reserve.toReserve" translate>{{ 'app.shared.buttons.cancel' }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-if="reserveSuccess" class="alert alert-success">{{ 'app.public.events_show.thank_you_your_payment_has_been_successfully_registered' | translate }}<br>
|
<div ng-if="reserveSuccess" class="alert alert-success">{{ 'app.public.events_show.thank_you_your_payment_has_been_successfully_registered' | translate }}<br>
|
||||||
|
@ -1,65 +1,53 @@
|
|||||||
<section class="home-events">
|
<section class="home-events">
|
||||||
<h4 class="text-sm m-t-sm">{{ 'app.public.home.fablab_s_next_events' | translate }} <a ui-sref="app.public.events_list" class="pull-right"><i class="fa fa-tags"></i> {{ 'app.public.home.every_events' | translate }}</a></h4>
|
<h4 class="text-sm m-t-sm">
|
||||||
|
{{ 'app.public.home.fablab_s_next_events' | translate }}
|
||||||
|
<a ui-sref="app.public.events_list">
|
||||||
|
<i class="fa fa-tags"></i>{{ 'app.public.home.every_events' | translate }}
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
<div class="row" ng-repeat="event in (upcomingEvents.length/3 | array)">
|
<div class="home-events-list" ng-repeat="event in (upcomingEvents.length/3 | array)">
|
||||||
|
<div class="Event" ng-repeat="event in upcomingEvents.slice(3*$index, 3*$index + 3)" ui-sref="app.public.events_show({id: event.id})">
|
||||||
<div class="col-xs-12 col-sm-6 col-md-6 col-lg-4" ng-repeat="event in upcomingEvents.slice(3*$index, 3*$index + 3)">
|
<div class="Event-picture">
|
||||||
|
|
||||||
|
|
||||||
<div class="widget panel panel-default" ui-sref="app.public.events_show({id: event.id})">
|
|
||||||
<div class="panel-heading picture" style="background-image: url({{event.event_image_medium}});" >
|
|
||||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:'Font Awesome 5 Free'/icon" bs-holder ng-if="!event.event_image" class="img-responsive">
|
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:'Font Awesome 5 Free'/icon" bs-holder ng-if="!event.event_image" class="img-responsive">
|
||||||
|
<img ng-if="event.event_image" src="{{event.event_image_medium}}">
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body" style="heigth:170px;">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-9">
|
|
||||||
<h1 class="m-b">{{event.title}}</h1>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-3">
|
|
||||||
<span class="v-middle badge text-xs" ng-class="'bg-{{event.category.slug}}'">{{event.category.name}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="event-description" ng-bind-html="event.description | simpleText | humanize : 500 | breakFilter"></p>
|
|
||||||
|
|
||||||
<hr/>
|
<div class="Event-desc">
|
||||||
<div class="row">
|
<h3>{{event.title}}</h3>
|
||||||
<div class="col-sm-6 row m-b-sm">
|
<span class="v-middle badge text-xs" ng-class="'bg-{{event.category.slug}}'">{{event.category.name}}</span>
|
||||||
<i class="fa fa-calendar red col-xs-3 padder-icon"></i>
|
<p ng-bind-html="event.description | simpleText | humanize : 500 | breakFilter"></p>
|
||||||
<h6 class="m-n col-xs-9 " ng-hide="isOneDayEvent(event)">{{ 'app.public.home.from_date_to_date' | translate:{START:(event.start_date | amDateFormat:'L'), END:(event.end_date | amDateFormat:'L')} }}</h6>
|
|
||||||
<h6 class="m-n col-xs-9 " ng-show="isOneDayEvent(event)">{{ 'app.public.home.on_the_date' | translate:{DATE:(event.start_date | amDateFormat:'L')} }}</h6>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 row m-b-sm">
|
|
||||||
<i class="fa fa-clock-o red col-xs-3 padder-icon"></i>
|
<div class="Event-info">
|
||||||
<h6 class="m-n col-xs-9">
|
<div class="Event-info-item">
|
||||||
|
<i class="fa fa-calendar"></i>
|
||||||
|
<h6 class="" ng-hide="isOneDayEvent(event)">{{ 'app.public.home.from_date_to_date' | translate:{START:(event.start_date | amDateFormat:'L'), END:(event.end_date | amDateFormat:'L')} }}</h6>
|
||||||
|
<h6 class="" ng-show="isOneDayEvent(event)">{{ 'app.public.home.on_the_date' | translate:{DATE:(event.start_date | amDateFormat:'L')} }}</h6>
|
||||||
|
</div>
|
||||||
|
<div class="Event-info-item">
|
||||||
|
<i class="fas fa-clock"></i>
|
||||||
|
<h6 class="">
|
||||||
<span ng-if="event.all_day == 'true'" translate>{{ 'app.public.home.all_day' }}</span>
|
<span ng-if="event.all_day == 'true'" translate>{{ 'app.public.home.all_day' }}</span>
|
||||||
<span ng-if="event.all_day == 'false'">{{ 'app.public.home.from_time_to_time' | translate:{START:(event.start_date | amDateFormat:'LT'), END:(event.end_date | amDateFormat:'LT')} }}</span>
|
<span ng-if="event.all_day == 'false'">{{ 'app.public.home.from_time_to_time' | translate:{START:(event.start_date | amDateFormat:'LT'), END:(event.end_date | amDateFormat:'LT')} }}</span>
|
||||||
</h6>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="Event-info-item">
|
||||||
|
<i class="fa fa-user"></i>
|
||||||
<div class="row">
|
<h6 class="">
|
||||||
<div class="col-sm-6 row m-b-sm">
|
|
||||||
<i class="fa fa-user red col-xs-3 padder-icon"></i>
|
|
||||||
<h6 class="m-n col-xs-9 ">
|
|
||||||
<span ng-if="event.nb_free_places > 0">{{ 'app.public.home.still_available' | translate }} {{event.nb_free_places}}</span>
|
<span ng-if="event.nb_free_places > 0">{{ 'app.public.home.still_available' | translate }} {{event.nb_free_places}}</span>
|
||||||
<span ng-if="!event.nb_total_places" translate>{{ 'app.public.home.without_reservation' }}</span>
|
<span ng-if="!event.nb_total_places" translate>{{ 'app.public.home.without_reservation' }}</span>
|
||||||
<span ng-if="event.nb_total_places > 0 && event.nb_free_places <= 0" translate>{{ 'app.public.home.event_full' }}</span>
|
<span ng-if="event.nb_total_places > 0 && event.nb_free_places <= 0" translate>{{ 'app.public.home.event_full' }}</span>
|
||||||
</h6>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 row m-b-sm">
|
<div class="Event-info-item">
|
||||||
<i class="fa fa-bookmark red col-xs-3 padder-icon"></i>
|
<i class="fa fa-bookmark"></i>
|
||||||
<h6 class="m-n col-xs-9">
|
<h6 class="">
|
||||||
<span ng-if="event.amount == 0" translate>{{ 'app.public.home.free_admission' }}</span>
|
<span ng-if="event.amount == 0" translate>{{ 'app.public.home.free_admission' }}</span>
|
||||||
<span ng-if="event.amount > 0">{{ 'app.public.home.full_price' | translate }} {{event.amount | currency}}</span>
|
<span ng-if="event.amount > 0">{{ 'app.public.home.full_price' | translate }} {{event.amount | currency}}</span>
|
||||||
</h6>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center clearfix ">
|
|
||||||
<div class="btn btn-lg btn-warning bg-white b-2x rounded m-t-sm m-b-sm upper text-sm width-70" ui-sref="app.public.events_show({id: event.id})" ><span translate>{{ 'app.shared.buttons.consult' }}</span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -18,10 +18,10 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="m-t-sm m-b-sm text-center" ng-if="!isAuthenticated()">
|
<div class="m-t-sm m-b-sm text-center" ng-if="!isAuthenticated()">
|
||||||
<button href="#" ng-click="signup($event)" class="btn btn-warning-full width-70 font-sbold rounded text-sm" translate>{{ 'app.public.home.create_an_account' }}</button>
|
<button ng-click="signup($event)" class="btn btn-warning-full width-70 font-sbold rounded text-sm" translate>{{ 'app.public.home.create_an_account' }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="m-t-sm m-b-sm text-center" ng-if="isAuthenticated()">
|
<div class="m-t-sm m-b-sm text-center" ng-if="isAuthenticated()">
|
||||||
<button href="#" ui-sref="app.logged.members" class="btn btn-warning-full width-70 font-sbold rounded text-sm" translate>{{ 'app.public.home.discover_members' }}</button>
|
<button ui-sref="app.logged.members" class="btn btn-warning-full width-70 font-sbold rounded text-sm" translate>{{ 'app.public.home.discover_members' }}</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-md-1 hidden-xs">
|
<div class="col-md-1 hidden-xs">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8 b-l b-r">
|
<div class="col-md-8 b-l b-r">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-7 b-l b-r-md">
|
<div class="col-xs-10 col-sm-10 col-md-7 b-l b-r-md">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
operator="currentUser">
|
operator="currentUser">
|
||||||
</plans-list>
|
</plans-list>
|
||||||
|
|
||||||
<a class="m-t-lg btn btn-small btn-default pull-right" href="#" ng-click="doNotSubscribePlan($event)">{{ 'app.shared.plan_subscribe.do_not_subscribe' | translate }} <i class="fa fa-long-arrow-right"></i></a>
|
<a class="m-t-lg btn btn-small btn-default pull-right" ng-click="doNotSubscribePlan($event)">{{ 'app.shared.plan_subscribe.do_not_subscribe' | translate }} <i class="fa fa-long-arrow-right"></i></a>
|
||||||
|
|
||||||
<div class="row row-centered m-t-lg">
|
<div class="row row-centered m-t-lg">
|
||||||
<div class="col-xs-12 col-md-12 col-lg-10 col-centered no-gutter">
|
<div class="col-xs-12 col-md-12 col-lg-10 col-centered no-gutter">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l ">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l ">
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<h3 translate>{{ 'app.logged.profile_completion.do_you_already_have_an_account' }}</h3>
|
<h3 translate>{{ 'app.logged.profile_completion.do_you_already_have_an_account' }}</h3>
|
||||||
<p ng-hide="hasDuplicate()" translate>{{ 'app.logged.profile_completion.do_not_fill_the_form_beside_but_specify_here_the_code_you_ve_received_by_email_to_recover_your_access' }}</p>
|
<p ng-hide="hasDuplicate()" translate>{{ 'app.logged.profile_completion.do_not_fill_the_form_beside_but_specify_here_the_code_you_ve_received_by_email_to_recover_your_access' }}</p>
|
||||||
<p ng-show="hasDuplicate()" translate>{{ 'app.logged.profile_completion.just_specify_code_here_to_recover_access' }}</p>
|
<p ng-show="hasDuplicate()" translate>{{ 'app.logged.profile_completion.just_specify_code_here_to_recover_access' }}</p>
|
||||||
<p class="pull-right"><a href="#" ng-click="resendCode($event)" translate>{{ 'app.logged.profile_completion.i_did_not_receive_the_code' }}</a></p>
|
<p class="pull-right"><a ng-click="resendCode($event)" translate>{{ 'app.logged.profile_completion.i_did_not_receive_the_code' }}</a></p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-3 col-lg-offset-1 hidden-md col-sm-3 col-sm-offset-1"></div>
|
<div class="col-lg-3 col-lg-offset-1 hidden-md col-sm-3 col-sm-offset-1"></div>
|
||||||
<div class="col-lg-offset-1 col-lg-6 col-md-12 col-sm-offset-1 col-sm-6">
|
<div class="col-lg-offset-1 col-lg-6 col-md-12 col-sm-offset-1 col-sm-6">
|
||||||
|
@ -87,7 +87,7 @@
|
|||||||
{{ 'app.shared.project.step_N' | translate:{ INDEX:step.step_nb } }}/{{totalSteps}} <i class="fa fa-caret-down" aria-hidden="true"></i>
|
{{ 'app.shared.project.step_N' | translate:{ INDEX:step.step_nb } }}/{{totalSteps}} <i class="fa fa-caret-down" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="single-button">
|
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="single-button">
|
||||||
<li role="menuitem" ng-repeat="step_idx in intArray(1, totalSteps +1)"><a href="#" ng-click="changeStepIndex($event, step, step_idx)">{{ 'app.shared.project.step_N' | translate:{ INDEX:step_idx } }}</a></li>
|
<li role="menuitem" ng-repeat="step_idx in intArray(1, totalSteps +1)"><a ng-click="changeStepIndex($event, step, step_idx)">{{ 'app.shared.project.step_N' | translate:{ INDEX:step_idx } }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<span class="label label-warning m-t m-b"></span>
|
<span class="label label-warning m-t m-b"></span>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||||
@ -153,7 +153,7 @@
|
|||||||
<section class="widget b-t">
|
<section class="widget b-t">
|
||||||
|
|
||||||
<div class="widget-content text-center m-t">
|
<div class="widget-content text-center m-t">
|
||||||
<a href="#" ng-click="signalAbuse($event)"><i class="fa fa-warning"></i> {{ 'app.public.projects_show.report_an_abuse' | translate }}</a>
|
<a ng-click="signalAbuse($event)"><i class="fa fa-warning"></i> {{ 'app.public.projects_show.report_an_abuse' | translate }}</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
@ -133,7 +133,7 @@
|
|||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="font-sbold text-u-c">{{ 'app.shared.cart.datetime_to_time' | translate:{START_DATETIME:(events.modifiable.start | amDateFormat:'LLLL'), END_TIME:(events.modifiable.end | amDateFormat:'LT') } }}</div>
|
<div class="font-sbold text-u-c">{{ 'app.shared.cart.datetime_to_time' | translate:{START_DATETIME:(events.modifiable.start | amDateFormat:'LLLL'), END_TIME:(events.modifiable.end | amDateFormat:'LT') } }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="cancelModifySlot($event)" translate>{{ 'app.shared.cart.cancel_my_modification' }}</a></div>
|
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" ng-click="cancelModifySlot($event)" translate>{{ 'app.shared.cart.cancel_my_modification' }}</a></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="widget-content no-bg">
|
<div class="widget-content no-bg">
|
||||||
@ -145,7 +145,7 @@
|
|||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="font-sbold text-u-c">{{ 'app.shared.cart.datetime_to_time' | translate:{START_DATETIME:(events.placable.start | amDateFormat:'LLLL'), END_TIME:(events.placable.end | amDateFormat:'LT') } }}</div>
|
<div class="font-sbold text-u-c">{{ 'app.shared.cart.datetime_to_time' | translate:{START_DATETIME:(events.placable.start | amDateFormat:'LLLL'), END_TIME:(events.placable.end | amDateFormat:'LT') } }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="removeSlotToPlace($event)" translate>{{ 'app.shared.cart.cancel_my_selection' }}</a></div>
|
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" ng-click="removeSlotToPlace($event)" translate>{{ 'app.shared.cart.cancel_my_selection' }}</a></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-if="events.placable && (events.modifiable.tags.length > 0 || events.placable.tags.length > 0)" ng-class="{'panel panel-danger bg-red': tagMissmatch()}">
|
<div ng-if="events.placable && (events.modifiable.tags.length > 0 || events.placable.tags.length > 0)" ng-class="{'panel panel-danger bg-red': tagMissmatch()}">
|
||||||
|
@ -42,9 +42,9 @@
|
|||||||
translate-attr="{ placeholder: 'app.public.common.your_password' }"
|
translate-attr="{ placeholder: 'app.public.common.your_password' }"
|
||||||
ng-minlength="8"/>
|
ng-minlength="8"/>
|
||||||
</div>
|
</div>
|
||||||
<a href="#" ng-click="openResetPassword($event)" class="text-xs" translate translate-default="Forgotten password">{{ 'app.public.common.password_forgotten' }}</a>
|
<a ng-click="openResetPassword($event)" class="text-xs" translate translate-default="Forgotten password">{{ 'app.public.common.password_forgotten' }}</a>
|
||||||
<span ng-if="confirmationRequired">
|
<span ng-if="confirmationRequired">
|
||||||
<br><a href="#" ng-click="openConfirmationNewModal($event)" class="text-xs" translate translate-default="Confirm account">{{ 'app.public.common.confirm_my_account' }}</a>
|
<br><a ng-click="openConfirmationNewModal($event)" class="text-xs" translate translate-default="Confirm account">{{ 'app.public.common.confirm_my_account' }}</a>
|
||||||
</span>
|
</span>
|
||||||
<div class="alert alert-warning m-t-sm m-b-none text-xs p-sm" ng-show='isCapsLockOn' role="alert">
|
<div class="alert alert-warning m-t-sm m-b-none text-xs p-sm" ng-show='isCapsLockOn' role="alert">
|
||||||
<i class="fa fa-warning"></i>
|
<i class="fa fa-warning"></i>
|
||||||
@ -63,7 +63,7 @@
|
|||||||
<p class="text-center font-sbold">
|
<p class="text-center font-sbold">
|
||||||
<span translate translate-default="Not registered?">{{ 'app.public.common.not_registered_to_the_fablab' }}</span>
|
<span translate translate-default="Not registered?">{{ 'app.public.common.not_registered_to_the_fablab' }}</span>
|
||||||
<br/>
|
<br/>
|
||||||
<a href="#" ng-click="openSignup($event)" class="text-u-l" translate translate-default="Create an account">{{ 'app.public.common.create_an_account' }}</a></br>
|
<a ng-click="openSignup($event)" class="text-u-l" translate translate-default="Create an account">{{ 'app.public.common.create_an_account' }}</a></br>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
<li><a class="text-black pointer" ng-click="logout($event)"><i class="fa fa-power-off"></i> {{ 'app.public.common.sign_out' | translate }}</a></li>
|
<li><a class="text-black pointer" ng-click="logout($event)"><i class="fa fa-power-off"></i> {{ 'app.public.common.sign_out' | translate }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li ng-if="!isAuthenticated()"><a class="font-sbold label text-md pointer" ng-click="signup($event)"><i class="fa fa-rocket"></i> {{ 'app.public.common.sign_up' | translate }}</a></li>
|
<li ng-if="!isAuthenticated() && registrationEnabled()"><a class="font-sbold label text-md pointer" ng-click="signup($event)"><i class="fa fa-rocket"></i> {{ 'app.public.common.sign_up' | translate }}</a></li>
|
||||||
<li ng-if="!isAuthenticated()">
|
<li ng-if="!isAuthenticated()">
|
||||||
<a class="font-sbold label text-md pointer" ng-click="login($event)"><i class="fa fa-sign-in"></i> {{ 'app.public.common.sign_in' | translate }}</a>
|
<a class="font-sbold label text-md pointer" ng-click="login($event)"><i class="fa fa-sign-in"></i> {{ 'app.public.common.sign_in' | translate }}</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -7,12 +7,12 @@
|
|||||||
<nav class="nav-primary hidden-xs">
|
<nav class="nav-primary hidden-xs">
|
||||||
<ul class="nav nav-main m-t-xs" data-ride="collapse">
|
<ul class="nav nav-main m-t-xs" data-ride="collapse">
|
||||||
<!-- Disconnected user menu for small devices -->
|
<!-- Disconnected user menu for small devices -->
|
||||||
<li class="hidden-sm hidden-md hidden-lg" ng-if-start="!isAuthenticated()">
|
<li class="hidden-sm hidden-md hidden-lg" ng-if="!isAuthenticated() && registrationEnabled()">
|
||||||
<a class="auto pointer" ng-click="signup($event)">
|
<a class="auto pointer" ng-click="signup($event)">
|
||||||
<i class="fa fa-rocket"></i> <span translate>{{ 'app.public.common.sign_up' }}</span>
|
<i class="fa fa-rocket"></i> <span translate>{{ 'app.public.common.sign_up' }}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="hidden-sm hidden-md hidden-lg" ng-if-end>
|
<li class="hidden-sm hidden-md hidden-lg" ng-if="!isAuthenticated()">
|
||||||
<a class="auto pointer" ng-click="login($event)">
|
<a class="auto pointer" ng-click="login($event)">
|
||||||
<i class="fa fa-sign-in"></i> <span translate>{{ 'app.public.common.sign_in' }}</span>
|
<i class="fa fa-sign-in"></i> <span translate>{{ 'app.public.common.sign_in' }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
<ng-repeat ng-repeat="network in social.networks.slice(0,3)">
|
<ng-repeat ng-repeat="network in social.networks.slice(0,3)">
|
||||||
<social-link network="{{network}}" user="user"></social-link>
|
<social-link network="{{network}}" user="user"></social-link>
|
||||||
</ng-repeat>
|
</ng-repeat>
|
||||||
<a href="#" ng-click="social.showAllLinks = !social.showAllLinks">
|
<a ng-click="social.showAllLinks = !social.showAllLinks">
|
||||||
<i class="fa fa-plus" ng-show="!social.showAllLinks"></i>
|
<i class="fa fa-plus" ng-show="!social.showAllLinks"></i>
|
||||||
<i class="fa fa-minus" ng-show="social.showAllLinks"></i>
|
<i class="fa fa-minus" ng-show="social.showAllLinks"></i>
|
||||||
</a>
|
</a>
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
required
|
required
|
||||||
bs-jasny-fileinput>
|
bs-jasny-fileinput>
|
||||||
</span>
|
</span>
|
||||||
<a href="#" class="btn btn-danger fileinput-exists" data-dismiss="fileinput" translate>{{ 'app.shared.buttons.delete' }}</a>
|
<a class="btn btn-danger fileinput-exists" data-dismiss="fileinput" translate>{{ 'app.shared.buttons.delete' }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-md-1 hidden-xs">
|
<div class="col-md-1 hidden-xs">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8 b-l b-r">
|
<div class="col-md-8 b-l b-r">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-md-1 hidden-xs">
|
<div class="col-md-1 hidden-xs">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8 b-l b-r">
|
<div class="col-md-8 b-l b-r">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-7 b-l b-r-md">
|
<div class="col-xs-10 col-sm-10 col-md-7 b-l b-r-md">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
<section class="heading-btn">
|
<section class="heading-btn">
|
||||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||||
@ -41,7 +41,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 no-padder">
|
<div class="col-sm-6 no-padder">
|
||||||
<div class="btn btn-default btn-block padder-v no-b red" ng-click="showTraining(training)">
|
<div class="btn btn-default btn-block padder-v no-b red" ng-click="showTraining(training)">
|
||||||
<i class="fa fa-eye"></i> {{ 'app.shared.buttons.consult' | translate }}
|
<i class="fas fa-eye"></i> {{ 'app.shared.buttons.consult' | translate }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user