1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-19 13:54:25 +01:00

address required

- Ability to set the address as a mandatory field
- The address is new requested when creating an account
- The profile completion page is less fuzzy for people landing on it without enabled SSO
This commit is contained in:
Sylvain 2021-03-23 11:49:05 +01:00
parent f535be5b05
commit 7099f1f317
19 changed files with 94 additions and 33 deletions

View File

@ -2,6 +2,9 @@
## Next release
- Ability to disable the trainings module
- Ability to set the address as a mandatory field
- The address is new requested when creating an account
- The profile completion page is less fuzzy for people landing on it without enabled SSO
- Prevent showing error message when testing for old versions during upgrade
- In the email notification, sent to admins on account creation, show the group of the user
- More explanations in the setup script

View File

@ -49,6 +49,7 @@ class API::AuthProvidersController < API::ApiController
def active
authorize AuthProvider
@provider = AuthProvider.active
@previous = AuthProvider.previous
end

View File

@ -41,7 +41,8 @@ class ApplicationController < ActionController::Base
{
profile_attributes: %i[phone last_name first_name interest software_mastered],
invoicing_profile_attributes: [
organization_attributes: [:name, address_attributes: [:address]]
organization_attributes: [:name, address_attributes: [:address]],
address_attributes: [:address]
],
statistic_profile_attributes: %i[gender birthday]
},

View File

@ -650,8 +650,8 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
/**
* Controller used in the member edition page
*/
Application.Controllers.controller('EditMemberController', ['$scope', '$state', '$stateParams', 'Member', 'Training', 'dialogs', 'growl', 'Group', 'Subscription', 'CSRF', 'memberPromise', 'tagsPromise', '$uibModal', 'Plan', '$filter', '_t', 'walletPromise', 'transactionsPromise', 'activeProviderPromise', 'Wallet', 'phoneRequiredPromise',
function ($scope, $state, $stateParams, Member, Training, dialogs, growl, Group, Subscription, CSRF, memberPromise, tagsPromise, $uibModal, Plan, $filter, _t, walletPromise, transactionsPromise, activeProviderPromise, Wallet, phoneRequiredPromise) {
Application.Controllers.controller('EditMemberController', ['$scope', '$state', '$stateParams', 'Member', 'Training', 'dialogs', 'growl', 'Group', 'Subscription', 'CSRF', 'memberPromise', 'tagsPromise', '$uibModal', 'Plan', '$filter', '_t', 'walletPromise', 'transactionsPromise', 'activeProviderPromise', 'Wallet', 'settingsPromise',
function ($scope, $state, $stateParams, Member, Training, dialogs, growl, Group, Subscription, CSRF, memberPromise, tagsPromise, $uibModal, Plan, $filter, _t, walletPromise, transactionsPromise, activeProviderPromise, Wallet, settingsPromise) {
/* PUBLIC SCOPE */
// API URL where the form will be posted
@ -670,7 +670,10 @@ Application.Controllers.controller('EditMemberController', ['$scope', '$state',
$scope.password = { change: false };
// is the phone number required in _member_form?
$scope.phoneRequired = (phoneRequiredPromise.setting.value === 'true');
$scope.phoneRequired = (settingsPromise.phone_required === 'true');
// is the address required in _member_form?
$scope.addressRequired = (settingsPromise.address_required === 'true');
// the user subscription
if (($scope.user.subscribed_plan != null) && ($scope.user.subscription != null)) {
@ -990,8 +993,8 @@ Application.Controllers.controller('EditMemberController', ['$scope', '$state',
/**
* Controller used in the member's creation page (admin view)
*/
Application.Controllers.controller('NewMemberController', ['$scope', '$state', '$stateParams', 'Member', 'Training', 'Group', 'CSRF', 'phoneRequiredPromise',
function ($scope, $state, $stateParams, Member, Training, Group, CSRF, phoneRequiredPromise) {
Application.Controllers.controller('NewMemberController', ['$scope', '$state', '$stateParams', 'Member', 'Training', 'Group', 'CSRF', 'settingsPromise',
function ($scope, $state, $stateParams, Member, Training, Group, CSRF, settingsPromise) {
CSRF.setMetaTags();
/* PUBLIC SCOPE */
@ -1006,7 +1009,10 @@ Application.Controllers.controller('NewMemberController', ['$scope', '$state', '
$scope.password = { change: false };
// is the phone number required in _member_form?
$scope.phoneRequired = (phoneRequiredPromise.setting.value === 'true');
$scope.phoneRequired = (settingsPromise.phone_required === 'true');
// is the address required to sign-up?
$scope.addressRequired = (settingsPromise.address_required === 'true');
// Default member's profile parameters
$scope.user = {
@ -1109,8 +1115,8 @@ Application.Controllers.controller('ImportMembersResultController', ['$scope', '
/**
* Controller used in the admin creation page (admin view)
*/
Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'Admin', 'growl', '_t', 'phoneRequiredPromise',
function ($state, $scope, Admin, growl, _t, phoneRequiredPromise) {
Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'Admin', 'growl', '_t', 'settingsPromise',
function ($state, $scope, Admin, growl, _t, settingsPromise) {
// default admin profile
let getGender;
$scope.admin = {
@ -1131,7 +1137,10 @@ Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'A
};
// is the phone number required in _admin_form?
$scope.phoneRequired = (phoneRequiredPromise.setting.value === 'true');
$scope.phoneRequired = (settingsPromise.phone_required === 'true');
// is the address required in _admin_form?
$scope.addressRequired = (settingsPromise.address_required === 'true');
/**
* Shows the birthday datepicker

View File

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

View File

@ -203,6 +203,14 @@ Application.Controllers.controller('CompleteProfileController', ['$scope', '$roo
});
};
/**
* Hide the new account messages.
* If hidden, the page will be used only to complete the current user's profile.
*/
$scope.hideNewAccountConfirmation = function () {
return !$scope.activeProvider.previous_provider || $scope.activeProvider.previous_provider.id === $scope.activeProvider.id;
};
/* PRIVATE SCOPE */
/**

View File

@ -167,7 +167,7 @@ angular.module('application.router', ['ui.router'])
resolve: {
groups: ['Group', function (Group) { return Group.query().$promise; }],
activeProviderPromise: ['AuthProvider', function (AuthProvider) { return AuthProvider.active().$promise; }],
phoneRequiredPromise: ['Setting', function (Setting) { return Setting.get({ name: 'phone_required' }).$promise; }]
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['phone_required', 'address_required']" }).$promise; }]
}
})
.state('app.logged.dashboard.projects', {
@ -915,7 +915,7 @@ angular.module('application.router', ['ui.router'])
}
},
resolve: {
phoneRequiredPromise: ['Setting', function (Setting) { return Setting.get({ name: 'phone_required' }).$promise; }]
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['phone_required', 'address_required']" }).$promise; }]
}
})
.state('app.admin.members_import', {
@ -956,7 +956,7 @@ angular.module('application.router', ['ui.router'])
walletPromise: ['Wallet', '$stateParams', function (Wallet, $stateParams) { return Wallet.getWalletByUser({ user_id: $stateParams.id }).$promise; }],
transactionsPromise: ['Wallet', 'walletPromise', function (Wallet, walletPromise) { return Wallet.transactions({ id: walletPromise.id }).$promise; }],
tagsPromise: ['Tag', function (Tag) { return Tag.query().$promise; }],
phoneRequiredPromise: ['Setting', function (Setting) { return Setting.get({ name: 'phone_required' }).$promise; }]
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['phone_required', 'address_required']" }).$promise; }]
}
})
.state('app.admin.admins_new', {
@ -968,7 +968,7 @@ angular.module('application.router', ['ui.router'])
}
},
resolve: {
phoneRequiredPromise: ['Setting', function (Setting) { return Setting.get({ name: 'phone_required' }).$promise; }]
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['phone_required', 'address_required']" }).$promise; }]
}
})
.state('app.admin.managers_new', {
@ -1061,7 +1061,7 @@ angular.module('application.router', ['ui.router'])
"'booking_cancel_delay', 'main_color', 'secondary_color', 'spaces_module', 'twitter_analytics', " +
"'fablab_name', 'name_genre', 'reminder_enable', 'plans_module', 'confirmation_required', " +
"'reminder_delay', 'visibility_yearly', 'visibility_others', 'wallet_module', 'trainings_module', " +
"'display_name_enable', 'machines_sort_by', 'fab_analytics', 'statistics_module', " +
"'display_name_enable', 'machines_sort_by', 'fab_analytics', 'statistics_module', 'address_required', " +
"'link_name', 'home_content', 'home_css', 'phone_required', 'upcoming_events_shown']"
}).$promise;
}],

View File

@ -427,7 +427,7 @@
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.address' }}</h3>
<p class="alert alert-warning m-h-md" translate>
{{ 'app.admin.settings.address_required_info' }}
{{ 'app.admin.settings.address_required_info_html' }}
</p>
<div class="col-md-10 col-md-offset-1">
<boolean-setting name="address_required"

View File

@ -21,7 +21,7 @@
<div class="row no-gutter">
<div class="col-sm-12 col-md-12 b-r">
<div class="row">
<div class="row" ng-hide="hideNewAccountConfirmation()">
<div class="col-md-offset-2 col-md-8 m-t-md">
<section class="panel panel-default bg-light m-lg">
<div class="panel-body m-r">
@ -34,19 +34,19 @@
</section>
</div>
</div>
<div class="row col-md-2 col-md-offset-5 hidden-sm hidden-xs" ng-hide="user.merged_at">
<div class="row col-md-2 col-md-offset-5 hidden-sm hidden-xs" ng-hide="user.merged_at || hideNewAccountConfirmation()">
<p class="font-felt fleche-left text-lg upper text-center">
<img src="../../images/arrow-left.png" class="fleche-left visible-lg visible-md fleche-left-from-top" />
<span class="or" translate>{{ 'app.logged.profile_completion.or' }}</span>
<img src="../../images/arrow-left.png" class="fleche-right visible-lg visible-md fleche-right-from-top" />
</p>
</div>clear
</div>
<div class="row">
<div class="col-md-6">
<div class="m-lg panel panel-default bg-light pos-rlt" ng-hide="hasDuplicate()">
<div ng-class="{'disabling-overlay' : !!user.auth_token}">
<div class="panel-body">
<h3 translate>{{ 'app.logged.profile_completion.new_on_this_platform' }}</h3>
<h3 translate ng-hide="hideNewAccountConfirmation()">{{ 'app.logged.profile_completion.new_on_this_platform' }}</h3>
<p translate>{{ 'app.logged.profile_completion.please_fill_the_following_form'}}.</p>
<p class="text-italic">{{ 'app.logged.profile_completion.some_data_may_have_already_been_provided_by_provider_and_cannot_be_modified' | translate:{NAME:activeProvider.name} }}.<br/>
{{ 'app.logged.profile_completion.then_click_on_' | translate }} <strong translate>{{ 'app.shared.buttons.confirm_changes' }}</strong> {{ 'app.logged.profile_completion._to_start_using_the_application' | translate }}.</p>
@ -145,14 +145,14 @@
</div>
</section>
</div>
<div class="row col-xs-2 col-xs-offset-5 hidden-md hidden-lg">
<div class="row col-xs-2 col-xs-offset-5 hidden-md hidden-lg" ng-hide="hideNewAccountConfirmation()">
<p class="font-felt fleche-left text-lg upper text-center">
<span class="or" translate>{{ 'app.logged.profile_completion.or' }}</span>
</p>
</div>
<div class="col-md-6 m-t-3xl-on-md" ng-hide="user.merged_at">
<ng-include src="'/profile/_token.html'"></ng-include>
</div>
<div class="col-md-6 m-t-3xl-on-md" ng-hide="user.merged_at || hideNewAccountConfirmation()">
<ng-include src="'/profile/_token.html'"></ng-include>
</div>
</div>
</div>
</div>

View File

@ -224,7 +224,7 @@
<div class="form-group">
<div class="input-group">
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_invoicing' | translate }}"><i class="fa fa-map-marker"></i> </span>
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_invoicing' | translate }}"><i class="fa fa-map-marker"></i> <span class="exponent" ng-show="addressRequired"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
<input type="hidden"
name="user[invoicing_profile_attributes][address_attributes][id]"
ng-value="user.invoicing_profile.address.id" />
@ -234,7 +234,8 @@
class="form-control"
id="user_address"
ng-disabled="preventField['profile.address'] && user.invoicing_profile.address.address && !userForm['user[invoicing_profile_attributes][address_attributes][address]'].$dirty"
placeholder="{{ 'app.shared.user.address' | translate }}"/>
placeholder="{{ 'app.shared.user.address' | translate }}"
ng-required="addressRequired"/>
</div>
</div>

View File

@ -213,6 +213,24 @@
</div>
</div>
<div class="form-group required-row">
<div class="col-sm-12">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-map-marker"></i> </span>
<input type="text"
name="address"
ng-model="user.invoicing_profile_attributes.address_attributes.address"
class="form-control"
placeholder="{{ 'app.public.common.address' | translate }}"
ng-required="addressRequired"/>
</div>
<span ng-show="addressRequired" class="exponent help-cursor" title="{{ 'app.public.common.used_for_invoicing' | translate }}">
<i class="fa fa-asterisk" aria-hidden="true"></i>
</span>
<span class="help-block" ng-show="signupForm.address.$dirty && signupForm.address.$error.required" translate>{{ 'app.public.common.address_is_required' }}</span>
</div>
</div>
<div class="form-group" ng-class="{'has-error': signupForm.is_allow_contact.$dirty && signupForm.is_allow_contact.$invalid}">
<div class="col-sm-12 checkbox-group">
<input type="checkbox"

View File

@ -41,6 +41,11 @@ class AuthProvider < ApplicationRecord
end
end
## Return the previously active provider
def self.previous
find_by(status: 'previous')
end
## Get the provider matching the omniAuth strategy name
def self.from_strategy_name(strategy_name)
return SimpleAuthProvider.new if strategy_name.blank? || all.empty?

View File

@ -1 +1,3 @@
json.extract! auth_provider, :id, :name, :status, :providable_type, :strategy_name
# frozen_string_literal: true
json.extract! auth_provider, :id, :name, :status, :providable_type, :strategy_name

View File

@ -1,4 +1,9 @@
# frozen_string_literal: true
json.partial! 'api/auth_providers/auth_provider', auth_provider: @provider
json.previous_provider do
json.partial! 'api/auth_providers/auth_provider', auth_provider: @previous if @previous
end
json.mapping @provider.sso_fields
json.link_to_sso_profile @provider.link_to_sso_profile
if @provider.providable_type == DatabaseProvider.name

View File

@ -1111,7 +1111,7 @@ en:
phone_is_required: "Phone required"
phone_required_info: "You can define if the phone number should be required to register a new user on Fab-manager."
address: "Address"
address_required_info: "You can define if the address should be required to register a new user on Fab-manager. Please note that, depending on your country, the regulations may requires addresses for the invoices to be valid."
address_required_info_html: "You can define if the address should be required to register a new user on Fab-manager.<br/><strong>Please note</strong> that, depending on your country, the regulations may requires addresses for the invoices to be valid."
address_is_required: "Address is required"
captcha: "Captcha"
captcha_info_html: "You can setup a protection against robots, to prevent them creating members accounts. This protection is using Google reCAPTCHA. Sign up for <a href='http://www.google.com/recaptcha/admin' target='_blank'>an API key pair</a> to start using the captcha."

View File

@ -1073,6 +1073,7 @@ fr:
machines_sort_by: "l'ordre d'affichage des machines"
fab_analytics: "Fab Analytics"
phone_required: "téléphone requis"
address_required: "adresse requise"
tracking_id: "l'ID de suivi"
facebook_app_id: "l'App ID Facebook"
twitter_analytics: "compte Twitter analytics"
@ -1110,7 +1111,7 @@ fr:
phone_is_required: "Téléphone requis"
phone_required_info: "Vous pouvez définir si le numéro de téléphone doit être requis, lors de l'enregistrement d'un nouvel utilisateur sur Fab-manager."
address: "Adresse"
address_required_info: "Vous pouvez définir si l'adresse doit être requise, lors de l'enregistrement d'un nouvel utilisateur sur Fab-manager. Veuillez noter que, selon votre pays, la réglementation peut exiger des adresses pour que les factures soient valides."
address_required_info_html: "Vous pouvez définir si l'adresse doit être requise, lors de l'enregistrement d'un nouvel utilisateur sur Fab-manager.<br/><strong>Veuillez noter</strong> que, selon votre pays, la réglementation peut exiger des adresses pour que les factures soient valides."
address_is_required: "Adresse requise"
captcha: "Captcha"
captcha_info_html: "Vous pouvez mettre en place une protection contre les robots, pour les empêcher de créer des comptes membre. Cette protection utilise Google reCAPTCHA. Inscrivez vous pour obtenir <a href='http://www.google.com/recaptcha/admin' target='_blank'>une paire de clefs d'API</a> afin d'utiliser le captcha."

View File

@ -84,6 +84,8 @@ en:
birth_date_is_required: "Birth date is required."
phone_number: "Phone number"
phone_number_is_required: "Phone number is required."
address: "Address"
address_is_required: "Address is required"
i_authorize_Fablab_users_registered_on_the_site_to_contact_me: "I authorize FabLab users, registered on the site, to contact me"
i_accept_to_receive_information_from_the_fablab: "I accept to receive information from the FabLab"
i_ve_read_and_i_accept_: "I've read and I accept"

View File

@ -84,6 +84,8 @@ fr:
birth_date_is_required: "La date de naissance est requise."
phone_number: "Numéro de téléphone"
phone_number_is_required: "Le numéro de téléphone est requis."
address: "Adresse"
address_is_required: "L'adresse est requise"
i_authorize_Fablab_users_registered_on_the_site_to_contact_me: "J'autorise les utilisateurs du Fab Lab inscrits sur le site à me contacter"
i_accept_to_receive_information_from_the_fablab: "J'accepte de recevoir des informations du Fab Lab"
i_ve_read_and_i_accept_: "J'ai lu et j'accepte"

View File

@ -16,7 +16,7 @@ namespace :fablab do
raise "FATAL ERROR: the provider '#{args.provider}' is already enabled" if AuthProvider.active.name == args.provider
# disable previous provider
prev_prev = AuthProvider.find_by(status: 'previous')
prev_prev = AuthProvider.previous
prev_prev&.update_attribute(:status, 'pending')
AuthProvider.active.update_attribute(:status, 'previous')