mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2024-12-01 12:24:28 +01:00
Merge branch 'dev' for release 5.4.16
This commit is contained in:
commit
e98776d51f
18
.overcommit.yml
Normal file
18
.overcommit.yml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
PreCommit:
|
||||||
|
RuboCop:
|
||||||
|
enabled: true
|
||||||
|
on_warn: fail # Treat all warnings as failures
|
||||||
|
|
||||||
|
TrailingWhitespace:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
CommitMsg:
|
||||||
|
CapitalizedSubject:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
MessageFormat:
|
||||||
|
enabled: true
|
||||||
|
pattern: ^(\([a-z]+\) [\w ]+(\n\n.+)?)|(Version (\d+\.?)+)|(Merge branch .*)
|
||||||
|
expected_pattern_message: (type) title\n\ndescription
|
||||||
|
sample_message: (bug) no validation on date\n\nThe birthdate was not validated...
|
||||||
|
|
16
CHANGELOG.md
16
CHANGELOG.md
@ -1,6 +1,20 @@
|
|||||||
# Changelog Fab-manager
|
# Changelog Fab-manager
|
||||||
|
|
||||||
## next release
|
## v5.4.16 2022 August 24
|
||||||
|
|
||||||
|
- Updated portuguese translations
|
||||||
|
- Added automatic RuboCop validation on pre-commit
|
||||||
|
- Use union type instead of enum for SettingName
|
||||||
|
- Clarified documentation about default values for environment variables
|
||||||
|
- Updated documentation about the minimum RAM required (#385)
|
||||||
|
- Fix a bug: wrong variable reference in `SingleSignOnConcern:Merge_form_sso`
|
||||||
|
- Fix a bug: wrong focus behavior on text editor
|
||||||
|
- Fix a bug: trainings monitoring is not available
|
||||||
|
- Fix a bug: invalid password length verification in profile edtion form
|
||||||
|
- Fix a bug: invalid password verification in setup script
|
||||||
|
- Fix a bug: during setup, unable to chown the installation folder, if the current user does not have a self-named group
|
||||||
|
- Fix a bug: during setup, the current value in config/env is not shown
|
||||||
|
- Fix a bug: disabling/removing a group has side effects on other groups
|
||||||
|
|
||||||
## v5.4.15 2022 August 1
|
## v5.4.15 2022 August 1
|
||||||
|
|
||||||
|
5
Gemfile
5
Gemfile
@ -36,6 +36,7 @@ group :development do
|
|||||||
gem 'web-console', '>= 3.3.0'
|
gem 'web-console', '>= 3.3.0'
|
||||||
# Preview mail in the browser
|
# Preview mail in the browser
|
||||||
gem 'listen', '~> 3.0.5'
|
gem 'listen', '~> 3.0.5'
|
||||||
|
gem 'overcommit'
|
||||||
gem 'rb-readline'
|
gem 'rb-readline'
|
||||||
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
|
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
|
||||||
gem 'railroady'
|
gem 'railroady'
|
||||||
@ -50,9 +51,9 @@ group :test do
|
|||||||
gem 'faker'
|
gem 'faker'
|
||||||
gem 'minitest-reporters'
|
gem 'minitest-reporters'
|
||||||
gem 'pdf-reader'
|
gem 'pdf-reader'
|
||||||
|
gem 'rubyXL'
|
||||||
gem 'vcr', '6.0.0'
|
gem 'vcr', '6.0.0'
|
||||||
gem 'webmock'
|
gem 'webmock'
|
||||||
gem 'rubyXL'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
group :production, :staging do
|
group :production, :staging do
|
||||||
@ -66,8 +67,6 @@ 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_openid_connect'
|
gem 'omniauth_openid_connect'
|
||||||
|
@ -93,6 +93,7 @@ GEM
|
|||||||
caxlsx_rails (0.6.2)
|
caxlsx_rails (0.6.2)
|
||||||
actionpack (>= 3.1)
|
actionpack (>= 3.1)
|
||||||
caxlsx (>= 3.0)
|
caxlsx (>= 3.0)
|
||||||
|
childprocess (4.1.0)
|
||||||
chroma (0.2.0)
|
chroma (0.2.0)
|
||||||
cldr-plurals-runtime-rb (1.0.1)
|
cldr-plurals-runtime-rb (1.0.1)
|
||||||
coercible (1.0.0)
|
coercible (1.0.0)
|
||||||
@ -176,6 +177,7 @@ GEM
|
|||||||
image_processing (1.12.2)
|
image_processing (1.12.2)
|
||||||
mini_magick (>= 4.9.5, < 5)
|
mini_magick (>= 4.9.5, < 5)
|
||||||
ruby-vips (>= 2.0.17, < 3)
|
ruby-vips (>= 2.0.17, < 3)
|
||||||
|
iniparse (1.5.0)
|
||||||
jbuilder (2.10.0)
|
jbuilder (2.10.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
jbuilder_cache_multi (0.1.0)
|
jbuilder_cache_multi (0.1.0)
|
||||||
@ -272,6 +274,10 @@ GEM
|
|||||||
openlab_ruby (0.0.7)
|
openlab_ruby (0.0.7)
|
||||||
httparty (~> 0.20)
|
httparty (~> 0.20)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
|
overcommit (0.59.1)
|
||||||
|
childprocess (>= 0.6.3, < 5)
|
||||||
|
iniparse (~> 1.4)
|
||||||
|
rexml (~> 3.2)
|
||||||
parallel (1.19.1)
|
parallel (1.19.1)
|
||||||
parser (3.1.2.0)
|
parser (3.1.2.0)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
@ -532,6 +538,7 @@ DEPENDENCIES
|
|||||||
omniauth-rails_csrf_protection (~> 0.1)
|
omniauth-rails_csrf_protection (~> 0.1)
|
||||||
omniauth_openid_connect
|
omniauth_openid_connect
|
||||||
openlab_ruby
|
openlab_ruby
|
||||||
|
overcommit
|
||||||
pdf-reader
|
pdf-reader
|
||||||
pg
|
pg
|
||||||
pg_search
|
pg_search
|
||||||
|
@ -52,7 +52,13 @@ class API::TrainingsController < API::ApiController
|
|||||||
authorize Training
|
authorize Training
|
||||||
@training = Training.find(params[:id])
|
@training = Training.find(params[:id])
|
||||||
@availabilities = @training.availabilities
|
@availabilities = @training.availabilities
|
||||||
.includes(slots: { slots_reservations: { reservations: { statistic_profile: [:trainings, user: [:profile]] } } })
|
.includes(slots: {
|
||||||
|
slots_reservations: {
|
||||||
|
reservation: {
|
||||||
|
statistic_profile: [:trainings, { user: [:profile] }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
.where('slots_reservations.canceled_at': nil)
|
.where('slots_reservations.canceled_at': nil)
|
||||||
.order('availabilities.start_at DESC')
|
.order('availabilities.start_at DESC')
|
||||||
end
|
end
|
||||||
|
@ -55,7 +55,7 @@ export default class SettingAPI {
|
|||||||
itemData.localized = item.localized;
|
itemData.localized = item.localized;
|
||||||
}
|
}
|
||||||
|
|
||||||
map.set(item.name as SettingName, itemData);
|
map.set(item.name, itemData);
|
||||||
});
|
});
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
@ -20,17 +20,17 @@ const ReservationsDashboard: React.FC<ReservationsDashboardProps> = ({ onError,
|
|||||||
const [modules, setModules] = useState<Map<SettingName, string>>();
|
const [modules, setModules] = useState<Map<SettingName, string>>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
SettingAPI.query([SettingName.SpacesModule, SettingName.MachinesModule])
|
SettingAPI.query(['spaces_module', 'machines_module'])
|
||||||
.then(res => setModules(res))
|
.then(res => setModules(res))
|
||||||
.catch(error => onError(error));
|
.catch(error => onError(error));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="reservations-dashboard">
|
<div className="reservations-dashboard">
|
||||||
{modules?.get(SettingName.MachinesModule) !== 'false' && <CreditsPanel userId={userId} onError={onError} reservableType="Machine" />}
|
{modules?.get('machines_module') !== 'false' && <CreditsPanel userId={userId} onError={onError} reservableType="Machine" />}
|
||||||
{modules?.get(SettingName.SpacesModule) !== 'false' && <CreditsPanel userId={userId} onError={onError} reservableType="Space" />}
|
{modules?.get('spaces_module') !== 'false' && <CreditsPanel userId={userId} onError={onError} reservableType="Space" />}
|
||||||
{modules?.get(SettingName.MachinesModule) !== 'false' && <ReservationsPanel userId={userId} onError={onError} reservableType="Machine" />}
|
{modules?.get('machines_module') !== 'false' && <ReservationsPanel userId={userId} onError={onError} reservableType="Machine" />}
|
||||||
{modules?.get(SettingName.SpacesModule) !== 'false' && <ReservationsPanel userId={userId} onError={onError} reservableType="Space" />}
|
{modules?.get('spaces_module') !== 'false' && <ReservationsPanel userId={userId} onError={onError} reservableType="Space" />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -9,15 +9,16 @@ export interface AbstractFormItemProps<TFieldValues> extends PropsWithChildren<A
|
|||||||
tooltip?: ReactNode,
|
tooltip?: ReactNode,
|
||||||
className?: string,
|
className?: string,
|
||||||
disabled?: boolean|((id: string) => boolean),
|
disabled?: boolean|((id: string) => boolean),
|
||||||
onLabelClick?: (event: React.MouseEvent<HTMLLabelElement, MouseEvent>) => void,
|
onLabelClick?: (event: React.MouseEvent<HTMLParagraphElement, MouseEvent>) => void,
|
||||||
inLine?: boolean,
|
inLine?: boolean,
|
||||||
|
containerType?: 'label' | 'div'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This abstract component should not be used directly.
|
* This abstract component should not be used directly.
|
||||||
* Other forms components that are intended to be used with react-hook-form must extend this component.
|
* Other forms components that are intended to be used with react-hook-form must extend this component.
|
||||||
*/
|
*/
|
||||||
export const AbstractFormItem = <TFieldValues extends FieldValues>({ id, label, tooltip, className, disabled, error, warning, rules, formState, onLabelClick, inLine, children }: AbstractFormItemProps<TFieldValues>) => {
|
export const AbstractFormItem = <TFieldValues extends FieldValues>({ id, label, tooltip, className, disabled, error, warning, rules, formState, onLabelClick, inLine, containerType, children }: AbstractFormItemProps<TFieldValues>) => {
|
||||||
const [isDirty, setIsDirty] = useState<boolean>(false);
|
const [isDirty, setIsDirty] = useState<boolean>(false);
|
||||||
const [fieldError, setFieldError] = useState<{ message: string }>(error);
|
const [fieldError, setFieldError] = useState<{ message: string }>(error);
|
||||||
const [isDisabled, setIsDisabled] = useState<boolean>(false);
|
const [isDisabled, setIsDisabled] = useState<boolean>(false);
|
||||||
@ -52,16 +53,16 @@ export const AbstractFormItem = <TFieldValues extends FieldValues>({ id, label,
|
|||||||
* This function is called when the label is clicked.
|
* This function is called when the label is clicked.
|
||||||
* It is used to focus the input.
|
* It is used to focus the input.
|
||||||
*/
|
*/
|
||||||
function handleLabelClick (event: React.MouseEvent<HTMLLabelElement, MouseEvent>) {
|
function handleLabelClick (event: React.MouseEvent<HTMLParagraphElement, MouseEvent>) {
|
||||||
if (typeof onLabelClick === 'function') {
|
if (typeof onLabelClick === 'function') {
|
||||||
onLabelClick(event);
|
onLabelClick(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return React.createElement(containerType, { className: `form-item ${classNames}` }, (
|
||||||
<label className={`form-item ${classNames}`} onClick={handleLabelClick}>
|
<>
|
||||||
{(label && !inLine) && <div className='form-item-header'>
|
{(label && !inLine) && <div className='form-item-header'>
|
||||||
<p>{label}</p>
|
<p onClick={handleLabelClick}>{label}</p>
|
||||||
{tooltip && <div className="item-tooltip">
|
{tooltip && <div className="item-tooltip">
|
||||||
<span className="trigger"><i className="fa fa-question-circle" /></span>
|
<span className="trigger"><i className="fa fa-question-circle" /></span>
|
||||||
<div className="content">{tooltip}</div>
|
<div className="content">{tooltip}</div>
|
||||||
@ -79,6 +80,8 @@ export const AbstractFormItem = <TFieldValues extends FieldValues>({ id, label,
|
|||||||
</div>
|
</div>
|
||||||
{(isDirty && fieldError) && <div className="form-item-error">{fieldError.message}</div> }
|
{(isDirty && fieldError) && <div className="form-item-error">{fieldError.message}</div> }
|
||||||
{(isDirty && warning) && <div className="form-item-warning">{warning.message}</div> }
|
{(isDirty && warning) && <div className="form-item-warning">{warning.message}</div> }
|
||||||
</label>
|
</>
|
||||||
);
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AbstractFormItem.defaultProps = { containerType: 'label' };
|
||||||
|
@ -35,13 +35,14 @@ export const FormRichText = <TFieldValues extends FieldValues, TContext extends
|
|||||||
* We do not want the default behavior (focus the first child, which is the Bold button)
|
* We do not want the default behavior (focus the first child, which is the Bold button)
|
||||||
* but we want to focus the text edition area.
|
* but we want to focus the text edition area.
|
||||||
*/
|
*/
|
||||||
function focusTextEditor (event: React.MouseEvent<HTMLLabelElement, MouseEvent>) {
|
function focusTextEditor (event: React.MouseEvent<HTMLParagraphElement, MouseEvent>) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
textEditorRef.current.focus();
|
textEditorRef.current.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AbstractFormItem id={id} label={label} tooltip={tooltip}
|
<AbstractFormItem id={id} label={label} tooltip={tooltip}
|
||||||
|
containerType={'div'}
|
||||||
className={`form-rich-text ${className || ''}`}
|
className={`form-rich-text ${className || ''}`}
|
||||||
error={error} warning={warning} rules={rules}
|
error={error} warning={warning} rules={rules}
|
||||||
disabled={disabled} formState={formState} onLabelClick={focusTextEditor}>
|
disabled={disabled} formState={formState} onLabelClick={focusTextEditor}>
|
||||||
|
@ -11,7 +11,6 @@ import { useForm } from 'react-hook-form';
|
|||||||
import { FormSelect } from '../form/form-select';
|
import { FormSelect } from '../form/form-select';
|
||||||
import MemberAPI from '../../api/member';
|
import MemberAPI from '../../api/member';
|
||||||
import SettingAPI from '../../api/setting';
|
import SettingAPI from '../../api/setting';
|
||||||
import { SettingName } from '../../models/setting';
|
|
||||||
import UserLib from '../../lib/user';
|
import UserLib from '../../lib/user';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
@ -46,7 +45,7 @@ export const ChangeGroup: React.FC<ChangeGroupProps> = ({ user, onSuccess, onErr
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
GroupAPI.index({ disabled: false, admins: user?.role === 'admin' }).then(setGroups).catch(onError);
|
GroupAPI.index({ disabled: false, admins: user?.role === 'admin' }).then(setGroups).catch(onError);
|
||||||
MemberAPI.current().then(setOperator).catch(onError);
|
MemberAPI.current().then(setOperator).catch(onError);
|
||||||
SettingAPI.get(SettingName.UserChangeGroup).then((setting) => {
|
SettingAPI.get('user_change_group').then((setting) => {
|
||||||
setAllowedUserChangeGoup(setting.value === 'true');
|
setAllowedUserChangeGoup(setting.value === 'true');
|
||||||
}).catch(onError);
|
}).catch(onError);
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -10,7 +10,6 @@ import { Machine } from '../../models/machine';
|
|||||||
import { User } from '../../models/user';
|
import { User } from '../../models/user';
|
||||||
import { IApplication } from '../../models/application';
|
import { IApplication } from '../../models/application';
|
||||||
import SettingAPI from '../../api/setting';
|
import SettingAPI from '../../api/setting';
|
||||||
import { SettingName } from '../../models/setting';
|
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
@ -46,7 +45,7 @@ const ReserveButton: React.FC<ReserveButtonProps> = ({ currentUser, machineId, o
|
|||||||
// check the trainings after we retrieved the machine data
|
// check the trainings after we retrieved the machine data
|
||||||
useEffect(() => checkTraining(), [machine]);
|
useEffect(() => checkTraining(), [machine]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
SettingAPI.get(SettingName.PackOnlyForSubscription)
|
SettingAPI.get('pack_only_for_subscription')
|
||||||
.then(data => setIsPackOnlyForSubscription(data.value === 'true'))
|
.then(data => setIsPackOnlyForSubscription(data.value === 'true'))
|
||||||
.catch(error => onError(error));
|
.catch(error => onError(error));
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -12,7 +12,7 @@ import FormatLib from '../../lib/format';
|
|||||||
import { PaymentScheduleItemActions, TypeOnce } from './payment-schedule-item-actions';
|
import { PaymentScheduleItemActions, TypeOnce } from './payment-schedule-item-actions';
|
||||||
import { StripeElements } from '../payment/stripe/stripe-elements';
|
import { StripeElements } from '../payment/stripe/stripe-elements';
|
||||||
import SettingAPI from '../../api/setting';
|
import SettingAPI from '../../api/setting';
|
||||||
import { Setting, SettingName } from '../../models/setting';
|
import { Setting } from '../../models/setting';
|
||||||
|
|
||||||
interface PaymentSchedulesTableProps {
|
interface PaymentSchedulesTableProps {
|
||||||
paymentSchedules: Array<PaymentSchedule>,
|
paymentSchedules: Array<PaymentSchedule>,
|
||||||
@ -40,7 +40,7 @@ const PaymentSchedulesTable: React.FC<PaymentSchedulesTableProps> = ({ paymentSc
|
|||||||
const [gateway, setGateway] = useState<Setting>(null);
|
const [gateway, setGateway] = useState<Setting>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
SettingAPI.get(SettingName.PaymentGateway)
|
SettingAPI.get('payment_gateway')
|
||||||
.then(setting => setGateway(setting))
|
.then(setting => setGateway(setting))
|
||||||
.catch(error => onError(error));
|
.catch(error => onError(error));
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -13,7 +13,6 @@ import PriceAPI from '../../api/price';
|
|||||||
import WalletAPI from '../../api/wallet';
|
import WalletAPI from '../../api/wallet';
|
||||||
import { Invoice } from '../../models/invoice';
|
import { Invoice } from '../../models/invoice';
|
||||||
import SettingAPI from '../../api/setting';
|
import SettingAPI from '../../api/setting';
|
||||||
import { SettingName } from '../../models/setting';
|
|
||||||
import { GoogleTagManager } from '../../models/gtm';
|
import { GoogleTagManager } from '../../models/gtm';
|
||||||
import { ComputePriceResult } from '../../models/price';
|
import { ComputePriceResult } from '../../models/price';
|
||||||
import { Wallet } from '../../models/wallet';
|
import { Wallet } from '../../models/wallet';
|
||||||
@ -91,7 +90,7 @@ export const AbstractPaymentModal: React.FC<AbstractPaymentModalProps> = ({ isOp
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
mounted.current = true;
|
mounted.current = true;
|
||||||
CustomAssetAPI.get(CustomAssetName.CgvFile).then(asset => setCgv(asset));
|
CustomAssetAPI.get(CustomAssetName.CgvFile).then(asset => setCgv(asset));
|
||||||
SettingAPI.get(SettingName.PaymentGateway).then((setting) => {
|
SettingAPI.get('payment_gateway').then((setting) => {
|
||||||
// we capitalize the first letter of the name
|
// we capitalize the first letter of the name
|
||||||
if (setting.value) {
|
if (setting.value) {
|
||||||
setGateway(setting.value.replace(/^\w/, (c) => c.toUpperCase()));
|
setGateway(setting.value.replace(/^\w/, (c) => c.toUpperCase()));
|
||||||
|
@ -7,7 +7,7 @@ import { IApplication } from '../../models/application';
|
|||||||
import { ShoppingCart } from '../../models/payment';
|
import { ShoppingCart } from '../../models/payment';
|
||||||
import { User } from '../../models/user';
|
import { User } from '../../models/user';
|
||||||
import { PaymentSchedule } from '../../models/payment-schedule';
|
import { PaymentSchedule } from '../../models/payment-schedule';
|
||||||
import { Setting, SettingName } from '../../models/setting';
|
import { Setting } from '../../models/setting';
|
||||||
import { Invoice } from '../../models/invoice';
|
import { Invoice } from '../../models/invoice';
|
||||||
import SettingAPI from '../../api/setting';
|
import SettingAPI from '../../api/setting';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -35,7 +35,7 @@ const CardPaymentModal: React.FC<CardPaymentModalProps> = ({ isOpen, toggleModal
|
|||||||
const [gateway, setGateway] = useState<Setting>(null);
|
const [gateway, setGateway] = useState<Setting>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
SettingAPI.get(SettingName.PaymentGateway)
|
SettingAPI.get('payment_gateway')
|
||||||
.then(setting => setGateway(setting))
|
.then(setting => setGateway(setting))
|
||||||
.catch(error => onError(error));
|
.catch(error => onError(error));
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -5,7 +5,6 @@ import { GatewayFormProps } from '../abstract-payment-modal';
|
|||||||
import LocalPaymentAPI from '../../../api/local-payment';
|
import LocalPaymentAPI from '../../../api/local-payment';
|
||||||
import FormatLib from '../../../lib/format';
|
import FormatLib from '../../../lib/format';
|
||||||
import SettingAPI from '../../../api/setting';
|
import SettingAPI from '../../../api/setting';
|
||||||
import { SettingName } from '../../../models/setting';
|
|
||||||
import { CardPaymentModal } from '../card-payment-modal';
|
import { CardPaymentModal } from '../card-payment-modal';
|
||||||
import { PaymentSchedule } from '../../../models/payment-schedule';
|
import { PaymentSchedule } from '../../../models/payment-schedule';
|
||||||
import { HtmlTranslate } from '../../base/html-translate';
|
import { HtmlTranslate } from '../../base/html-translate';
|
||||||
@ -75,7 +74,7 @@ export const LocalPaymentForm: React.FC<GatewayFormProps> = ({ onSubmit, onSucce
|
|||||||
if (paymentSchedule && method === 'card') {
|
if (paymentSchedule && method === 'card') {
|
||||||
// check that the online payment is active
|
// check that the online payment is active
|
||||||
try {
|
try {
|
||||||
const online = await SettingAPI.get(SettingName.OnlinePaymentModule);
|
const online = await SettingAPI.get('online_payment_module');
|
||||||
if (online.value !== 'true') {
|
if (online.value !== 'true') {
|
||||||
return onError(t('app.admin.local_payment_form.online_payment_disabled'));
|
return onError(t('app.admin.local_payment_form.online_payment_disabled'));
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ import React, { FormEvent, FunctionComponent, useEffect, useRef, useState } from
|
|||||||
import KRGlue from '@lyracom/embedded-form-glue';
|
import KRGlue from '@lyracom/embedded-form-glue';
|
||||||
import { GatewayFormProps } from '../abstract-payment-modal';
|
import { GatewayFormProps } from '../abstract-payment-modal';
|
||||||
import SettingAPI from '../../../api/setting';
|
import SettingAPI from '../../../api/setting';
|
||||||
import { SettingName } from '../../../models/setting';
|
|
||||||
import PayzenAPI from '../../../api/payzen';
|
import PayzenAPI from '../../../api/payzen';
|
||||||
import {
|
import {
|
||||||
CreateTokenResponse,
|
CreateTokenResponse,
|
||||||
@ -27,10 +26,10 @@ export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onE
|
|||||||
const [loadingClass, setLoadingClass] = useState<'hidden' | 'loader' | 'loader-overlay'>('loader');
|
const [loadingClass, setLoadingClass] = useState<'hidden' | 'loader' | 'loader-overlay'>('loader');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
SettingAPI.query([SettingName.PayZenEndpoint, SettingName.PayZenPublicKey]).then(settings => {
|
SettingAPI.query(['payzen_endpoint', 'payzen_public_key']).then(settings => {
|
||||||
createToken().then(formToken => {
|
createToken().then(formToken => {
|
||||||
// Load the remote library
|
// Load the remote library
|
||||||
KRGlue.loadLibrary(settings.get(SettingName.PayZenEndpoint), settings.get(SettingName.PayZenPublicKey))
|
KRGlue.loadLibrary(settings.get('payzen_endpoint'), settings.get('payzen_public_key'))
|
||||||
.then(({ KR }) =>
|
.then(({ KR }) =>
|
||||||
KR.setFormConfig({
|
KR.setFormConfig({
|
||||||
formToken: formToken.formToken
|
formToken: formToken.formToken
|
||||||
|
@ -17,9 +17,9 @@ interface PayzenKeysFormProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// all settings related to PayZen that are requested by this form
|
// all settings related to PayZen that are requested by this form
|
||||||
const payZenSettings: Array<SettingName> = [SettingName.PayZenUsername, SettingName.PayZenPassword, SettingName.PayZenEndpoint, SettingName.PayZenHmacKey, SettingName.PayZenPublicKey];
|
const payzenSettings: Array<SettingName> = ['payzen_username', 'payzen_password', 'payzen_endpoint', 'payzen_hmac', 'payzen_public_key'];
|
||||||
// settings related the to PayZen REST API (server side)
|
// settings related to the PayZen REST API (server side)
|
||||||
const restApiSettings: Array<SettingName> = [SettingName.PayZenUsername, SettingName.PayZenPassword, SettingName.PayZenEndpoint, SettingName.PayZenHmacKey];
|
const restApiSettings: Array<SettingName> = ['payzen_username', 'payzen_password', 'payzen_endpoint', 'payzen_hmac'];
|
||||||
|
|
||||||
// Prevent multiples call to the payzen keys validation endpoint.
|
// Prevent multiples call to the payzen keys validation endpoint.
|
||||||
// this cannot be handled by a React state because of their asynchronous nature
|
// this cannot be handled by a React state because of their asynchronous nature
|
||||||
@ -32,7 +32,7 @@ const PayzenKeysForm: React.FC<PayzenKeysFormProps> = ({ onValidKeys, onInvalidK
|
|||||||
const { t } = useTranslation('admin');
|
const { t } = useTranslation('admin');
|
||||||
|
|
||||||
// values of the PayZen settings
|
// values of the PayZen settings
|
||||||
const [settings, updateSettings] = useImmer<Map<SettingName, string>>(new Map(payZenSettings.map(name => [name, ''])));
|
const [settings, updateSettings] = useImmer<Map<SettingName, string>>(new Map(payzenSettings.map(name => [name, ''])));
|
||||||
// Icon of the fieldset for the PayZen's keys concerning the REST API. Used to display if the key is valid.
|
// Icon of the fieldset for the PayZen's keys concerning the REST API. Used to display if the key is valid.
|
||||||
const [restApiAddOn, setRestApiAddOn] = useState<ReactNode>(null);
|
const [restApiAddOn, setRestApiAddOn] = useState<ReactNode>(null);
|
||||||
// Style class for the add-on icon, for the REST API
|
// Style class for the add-on icon, for the REST API
|
||||||
@ -46,7 +46,7 @@ const PayzenKeysForm: React.FC<PayzenKeysFormProps> = ({ onValidKeys, onInvalidK
|
|||||||
* When the component loads for the first time, initialize the keys with the values fetched from the API (if any)
|
* When the component loads for the first time, initialize the keys with the values fetched from the API (if any)
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
SettingAPI.query(payZenSettings).then(payZenKeys => {
|
SettingAPI.query(payzenSettings).then(payZenKeys => {
|
||||||
updateSettings(new Map(payZenKeys));
|
updateSettings(new Map(payZenKeys));
|
||||||
}).catch(error => console.error(error));
|
}).catch(error => console.error(error));
|
||||||
}, []);
|
}, []);
|
||||||
@ -78,7 +78,7 @@ const PayzenKeysForm: React.FC<PayzenKeysFormProps> = ({ onValidKeys, onInvalidK
|
|||||||
setPublicKeyAddOnClassName('key-invalid');
|
setPublicKeyAddOnClassName('key-invalid');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateSettings(draft => draft.set(SettingName.PayZenPublicKey, key));
|
updateSettings(draft => draft.set('payzen_public_key', key));
|
||||||
setPublicKeyAddOn(<i className="fa fa-check" />);
|
setPublicKeyAddOn(<i className="fa fa-check" />);
|
||||||
setPublicKeyAddOnClassName('key-valid');
|
setPublicKeyAddOnClassName('key-valid');
|
||||||
};
|
};
|
||||||
@ -94,9 +94,9 @@ const PayzenKeysForm: React.FC<PayzenKeysFormProps> = ({ onValidKeys, onInvalidK
|
|||||||
if (valid && !pendingKeysValidation) {
|
if (valid && !pendingKeysValidation) {
|
||||||
pendingKeysValidation = true;
|
pendingKeysValidation = true;
|
||||||
PayzenAPI.chargeSDKTest(
|
PayzenAPI.chargeSDKTest(
|
||||||
settings.get(SettingName.PayZenEndpoint),
|
settings.get('payzen_endpoint'),
|
||||||
settings.get(SettingName.PayZenUsername),
|
settings.get('payzen_username'),
|
||||||
settings.get(SettingName.PayZenPassword)
|
settings.get('payzen_password')
|
||||||
).then(result => {
|
).then(result => {
|
||||||
pendingKeysValidation = false;
|
pendingKeysValidation = false;
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ const PayzenKeysForm: React.FC<PayzenKeysFormProps> = ({ onValidKeys, onInvalidK
|
|||||||
/**
|
/**
|
||||||
* Assign the inputted key to the given settings
|
* Assign the inputted key to the given settings
|
||||||
*/
|
*/
|
||||||
const setApiKey = (setting: SettingName.PayZenUsername | SettingName.PayZenPassword | SettingName.PayZenEndpoint | SettingName.PayZenHmacKey) => {
|
const setApiKey = (setting: typeof restApiSettings[number]) => {
|
||||||
return (key: string) => {
|
return (key: string) => {
|
||||||
updateSettings(draft => draft.set(setting, key));
|
updateSettings(draft => draft.set(setting, key));
|
||||||
};
|
};
|
||||||
@ -148,7 +148,7 @@ const PayzenKeysForm: React.FC<PayzenKeysFormProps> = ({ onValidKeys, onInvalidK
|
|||||||
<label htmlFor="payzen_public_key">{ t('app.admin.invoices.payzen_keys_form.payzen_public_key') } *</label>
|
<label htmlFor="payzen_public_key">{ t('app.admin.invoices.payzen_keys_form.payzen_public_key') } *</label>
|
||||||
<FabInput id="payzen_public_key"
|
<FabInput id="payzen_public_key"
|
||||||
icon={<i className="fas fa-info" />}
|
icon={<i className="fas fa-info" />}
|
||||||
defaultValue={settings.get(SettingName.PayZenPublicKey)}
|
defaultValue={settings.get('payzen_public_key')}
|
||||||
onChange={testPublicKey}
|
onChange={testPublicKey}
|
||||||
addOn={publicKeyAddOn}
|
addOn={publicKeyAddOn}
|
||||||
addOnClassName={publicKeyAddOnClassName}
|
addOnClassName={publicKeyAddOnClassName}
|
||||||
@ -166,8 +166,8 @@ const PayzenKeysForm: React.FC<PayzenKeysFormProps> = ({ onValidKeys, onInvalidK
|
|||||||
<FabInput id="payzen_username"
|
<FabInput id="payzen_username"
|
||||||
type="number"
|
type="number"
|
||||||
icon={<i className="fas fa-user-alt" />}
|
icon={<i className="fas fa-user-alt" />}
|
||||||
defaultValue={settings.get(SettingName.PayZenUsername)}
|
defaultValue={settings.get('payzen_username')}
|
||||||
onChange={setApiKey(SettingName.PayZenUsername)}
|
onChange={setApiKey('payzen_username')}
|
||||||
debounce={200}
|
debounce={200}
|
||||||
required />
|
required />
|
||||||
</div>
|
</div>
|
||||||
@ -175,8 +175,8 @@ const PayzenKeysForm: React.FC<PayzenKeysFormProps> = ({ onValidKeys, onInvalidK
|
|||||||
<label htmlFor="payzen_password">{ t('app.admin.invoices.payzen_keys_form.payzen_password') } *</label>
|
<label htmlFor="payzen_password">{ t('app.admin.invoices.payzen_keys_form.payzen_password') } *</label>
|
||||||
<FabInput id="payzen_password"
|
<FabInput id="payzen_password"
|
||||||
icon={<i className="fas fa-key" />}
|
icon={<i className="fas fa-key" />}
|
||||||
defaultValue={settings.get(SettingName.PayZenPassword)}
|
defaultValue={settings.get('payzen_password')}
|
||||||
onChange={setApiKey(SettingName.PayZenPassword)}
|
onChange={setApiKey('payzen_password')}
|
||||||
debounce={200}
|
debounce={200}
|
||||||
required />
|
required />
|
||||||
</div>
|
</div>
|
||||||
@ -185,8 +185,8 @@ const PayzenKeysForm: React.FC<PayzenKeysFormProps> = ({ onValidKeys, onInvalidK
|
|||||||
<FabInput id="payzen_endpoint"
|
<FabInput id="payzen_endpoint"
|
||||||
type="url"
|
type="url"
|
||||||
icon={<i className="fas fa-link" />}
|
icon={<i className="fas fa-link" />}
|
||||||
defaultValue={settings.get(SettingName.PayZenEndpoint)}
|
defaultValue={settings.get('payzen_endpoint')}
|
||||||
onChange={setApiKey(SettingName.PayZenEndpoint)}
|
onChange={setApiKey('payzen_endpoint')}
|
||||||
debounce={200}
|
debounce={200}
|
||||||
required />
|
required />
|
||||||
</div>
|
</div>
|
||||||
@ -194,8 +194,8 @@ const PayzenKeysForm: React.FC<PayzenKeysFormProps> = ({ onValidKeys, onInvalidK
|
|||||||
<label htmlFor="payzen_hmac">{ t('app.admin.invoices.payzen_keys_form.payzen_hmac') } *</label>
|
<label htmlFor="payzen_hmac">{ t('app.admin.invoices.payzen_keys_form.payzen_hmac') } *</label>
|
||||||
<FabInput id="payzen_hmac"
|
<FabInput id="payzen_hmac"
|
||||||
icon={<i className="fas fa-subscript" />}
|
icon={<i className="fas fa-subscript" />}
|
||||||
defaultValue={settings.get(SettingName.PayZenHmacKey)}
|
defaultValue={settings.get('payzen_hmac')}
|
||||||
onChange={setApiKey(SettingName.PayZenHmacKey)}
|
onChange={setApiKey('payzen_hmac')}
|
||||||
debounce={200}
|
debounce={200}
|
||||||
required />
|
required />
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,21 +21,21 @@ interface PayzenSettingsProps {
|
|||||||
const PAYZEN_HIDDEN = 'HiDdEnHIddEnHIdDEnHiDdEnHIddEnHIdDEn';
|
const PAYZEN_HIDDEN = 'HiDdEnHIddEnHIdDEnHiDdEnHIddEnHIdDEn';
|
||||||
|
|
||||||
// settings related to PayZen that can be shown publicly
|
// settings related to PayZen that can be shown publicly
|
||||||
const payZenPublicSettings: Array<SettingName> = [SettingName.PayZenPublicKey, SettingName.PayZenEndpoint, SettingName.PayZenUsername];
|
const payZenPublicSettings: Array<SettingName> = ['payzen_public_key', 'payzen_endpoint', 'payzen_username'];
|
||||||
// settings related to PayZen that must be kept on server-side
|
// settings related to PayZen that must be kept on server-side
|
||||||
const payZenPrivateSettings: Array<SettingName> = [SettingName.PayZenPassword, SettingName.PayZenHmacKey];
|
const payZenPrivateSettings: Array<SettingName> = ['payzen_password', 'payzen_hmac'];
|
||||||
// other settings related to PayZen
|
// other settings related to PayZen
|
||||||
const payZenOtherSettings: Array<SettingName> = [SettingName.PayZenCurrency];
|
const payZenOtherSettings: Array<SettingName> = ['payzen_currency'];
|
||||||
// all PayZen settings
|
// all PayZen settings
|
||||||
const payZenSettings: Array<SettingName> = payZenPublicSettings.concat(payZenPrivateSettings).concat(payZenOtherSettings);
|
const payZenSettings: Array<SettingName> = payZenPublicSettings.concat(payZenPrivateSettings).concat(payZenOtherSettings);
|
||||||
|
|
||||||
// icons for the inputs of each setting
|
// icons for the inputs of each setting
|
||||||
const icons:Map<SettingName, string> = new Map([
|
const icons:Map<SettingName, string> = new Map([
|
||||||
[SettingName.PayZenHmacKey, 'subscript'],
|
['payzen_hmac', 'subscript'],
|
||||||
[SettingName.PayZenPassword, 'key'],
|
['payzen_password', 'key'],
|
||||||
[SettingName.PayZenUsername, 'user'],
|
['payzen_username', 'user'],
|
||||||
[SettingName.PayZenEndpoint, 'link'],
|
['payzen_endpoint', 'link'],
|
||||||
[SettingName.PayZenPublicKey, 'info']
|
['payzen_public_key', 'info']
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,11 +55,11 @@ export const PayzenSettings: React.FC<PayzenSettingsProps> = ({ onEditKeys, onCu
|
|||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
SettingAPI.query(payZenPublicSettings.concat(payZenOtherSettings)).then(payZenKeys => {
|
SettingAPI.query(payZenPublicSettings.concat(payZenOtherSettings)).then(payZenKeys => {
|
||||||
SettingAPI.isPresent(SettingName.PayZenPassword).then(pzPassword => {
|
SettingAPI.isPresent('payzen_password').then(pzPassword => {
|
||||||
SettingAPI.isPresent(SettingName.PayZenHmacKey).then(pzHmac => {
|
SettingAPI.isPresent('payzen_hmac').then(pzHmac => {
|
||||||
const map = new Map(payZenKeys);
|
const map = new Map(payZenKeys);
|
||||||
map.set(SettingName.PayZenPassword, pzPassword ? PAYZEN_HIDDEN : '');
|
map.set('payzen_password', pzPassword ? PAYZEN_HIDDEN : '');
|
||||||
map.set(SettingName.PayZenHmacKey, pzHmac ? PAYZEN_HIDDEN : '');
|
map.set('payzen_hmac', pzHmac ? PAYZEN_HIDDEN : '');
|
||||||
|
|
||||||
updateSettings(map);
|
updateSettings(map);
|
||||||
}).catch(error => { console.error(error); });
|
}).catch(error => { console.error(error); });
|
||||||
@ -81,7 +81,7 @@ export const PayzenSettings: React.FC<PayzenSettingsProps> = ({ onEditKeys, onCu
|
|||||||
const handleCurrencyUpdate = (value: string, validity?: ValidityState): void => {
|
const handleCurrencyUpdate = (value: string, validity?: ValidityState): void => {
|
||||||
if (!validity || validity.valid) {
|
if (!validity || validity.valid) {
|
||||||
setError('');
|
setError('');
|
||||||
updateSettings(draft => draft.set(SettingName.PayZenCurrency, value));
|
updateSettings(draft => draft.set('payzen_currency', value));
|
||||||
} else {
|
} else {
|
||||||
setError(t('app.admin.invoices.payment.payzen_settings.currency_error'));
|
setError(t('app.admin.invoices.payment.payzen_settings.currency_error'));
|
||||||
}
|
}
|
||||||
@ -92,9 +92,9 @@ export const PayzenSettings: React.FC<PayzenSettingsProps> = ({ onEditKeys, onCu
|
|||||||
* This will update the setting on the server.
|
* This will update the setting on the server.
|
||||||
*/
|
*/
|
||||||
const saveCurrency = (): void => {
|
const saveCurrency = (): void => {
|
||||||
SettingAPI.update(SettingName.PayZenCurrency, settings.get(SettingName.PayZenCurrency)).then(result => {
|
SettingAPI.update('payzen_currency', settings.get('payzen_currency')).then(result => {
|
||||||
setError('');
|
setError('');
|
||||||
updateSettings(draft => draft.set(SettingName.PayZenCurrency, result.value));
|
updateSettings(draft => draft.set('payzen_currency', result.value));
|
||||||
onCurrencyUpdateSuccess(result.value);
|
onCurrencyUpdateSuccess(result.value);
|
||||||
}, reason => {
|
}, reason => {
|
||||||
setError(t('app.admin.invoices.payment.payzen_settings.error_while_saving') + reason);
|
setError(t('app.admin.invoices.payment.payzen_settings.error_while_saving') + reason);
|
||||||
@ -130,7 +130,7 @@ export const PayzenSettings: React.FC<PayzenSettingsProps> = ({ onEditKeys, onCu
|
|||||||
<div className="payzen-currency-form">
|
<div className="payzen-currency-form">
|
||||||
<div className="currency-wrapper">
|
<div className="currency-wrapper">
|
||||||
<label htmlFor="payzen_currency">{t('app.admin.invoices.payment.payzen_settings.payzen_currency')}</label>
|
<label htmlFor="payzen_currency">{t('app.admin.invoices.payment.payzen_settings.payzen_currency')}</label>
|
||||||
<FabInput defaultValue={settings.get(SettingName.PayZenCurrency)}
|
<FabInput defaultValue={settings.get('payzen_currency')}
|
||||||
id="payzen_currency"
|
id="payzen_currency"
|
||||||
icon={<i className="fas fa-money-bill" />}
|
icon={<i className="fas fa-money-bill" />}
|
||||||
onChange={handleCurrencyUpdate}
|
onChange={handleCurrencyUpdate}
|
||||||
|
@ -38,7 +38,7 @@ export const SelectGatewayModal: React.FC<SelectGatewayModalModalProps> = ({ isO
|
|||||||
|
|
||||||
// request the configured gateway to the API
|
// request the configured gateway to the API
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
SettingAPI.get(SettingName.PaymentGateway).then(gateway => {
|
SettingAPI.get('payment_gateway').then(gateway => {
|
||||||
setSelectedGateway(gateway.value ? gateway.value : '');
|
setSelectedGateway(gateway.value ? gateway.value : '');
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
@ -73,8 +73,8 @@ export const SelectGatewayModal: React.FC<SelectGatewayModalModalProps> = ({ isO
|
|||||||
const handleValidStripeKeys = (publicKey: string, secretKey: string): void => {
|
const handleValidStripeKeys = (publicKey: string, secretKey: string): void => {
|
||||||
setGatewayConfig((prev) => {
|
setGatewayConfig((prev) => {
|
||||||
const newMap = new Map(prev);
|
const newMap = new Map(prev);
|
||||||
newMap.set(SettingName.StripeSecretKey, secretKey);
|
newMap.set('stripe_secret_key', secretKey);
|
||||||
newMap.set(SettingName.StripePublicKey, publicKey);
|
newMap.set('stripe_public_key', publicKey);
|
||||||
return newMap;
|
return newMap;
|
||||||
});
|
});
|
||||||
setPreventConfirmGateway(false);
|
setPreventConfirmGateway(false);
|
||||||
@ -100,7 +100,7 @@ export const SelectGatewayModal: React.FC<SelectGatewayModalModalProps> = ({ isO
|
|||||||
*/
|
*/
|
||||||
const updateSettings = (): void => {
|
const updateSettings = (): void => {
|
||||||
const settings = new Map<SettingName, string>(gatewayConfig);
|
const settings = new Map<SettingName, string>(gatewayConfig);
|
||||||
settings.set(SettingName.PaymentGateway, selectedGateway);
|
settings.set('payment_gateway', selectedGateway);
|
||||||
|
|
||||||
SettingAPI.bulkUpdate(settings, true).then(result => {
|
SettingAPI.bulkUpdate(settings, true).then(result => {
|
||||||
const errorResults = Array.from(result.values()).filter(item => !item.status);
|
const errorResults = Array.from(result.values()).filter(item => !item.status);
|
||||||
|
@ -1,7 +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, Stripe } from '@stripe/stripe-js';
|
import { loadStripe, Stripe } from '@stripe/stripe-js';
|
||||||
import { SettingName } from '../../../models/setting';
|
|
||||||
import SettingAPI from '../../../api/setting';
|
import SettingAPI from '../../../api/setting';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,7 +13,7 @@ export const StripeElements: React.FC = memo(({ children }) => {
|
|||||||
* 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('stripe_public_key').then(key => {
|
||||||
if (key?.value) {
|
if (key?.value) {
|
||||||
const promise = loadStripe(key.value);
|
const promise = loadStripe(key.value);
|
||||||
setStripe(promise);
|
setStripe(promise);
|
||||||
|
@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { HtmlTranslate } from '../../base/html-translate';
|
import { HtmlTranslate } from '../../base/html-translate';
|
||||||
import { FabInput } from '../../base/fab-input';
|
import { FabInput } from '../../base/fab-input';
|
||||||
import { Loader } from '../../base/loader';
|
import { Loader } from '../../base/loader';
|
||||||
import { SettingName } from '../../../models/setting';
|
|
||||||
import StripeAPI from '../../../api/external/stripe';
|
import StripeAPI from '../../../api/external/stripe';
|
||||||
import SettingAPI from '../../../api/setting';
|
import SettingAPI from '../../../api/setting';
|
||||||
|
|
||||||
@ -42,9 +41,9 @@ const StripeKeysForm: React.FC<StripeKeysFormProps> = ({ onValidKeys, onInvalidK
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
mounted.current = true;
|
mounted.current = true;
|
||||||
|
|
||||||
SettingAPI.query([SettingName.StripePublicKey, SettingName.StripeSecretKey]).then(stripeKeys => {
|
SettingAPI.query(['stripe_public_key', 'stripe_secret_key']).then(stripeKeys => {
|
||||||
setPublicKey(stripeKeys.get(SettingName.StripePublicKey));
|
setPublicKey(stripeKeys.get('stripe_public_key'));
|
||||||
setSecretKey(stripeKeys.get(SettingName.StripeSecretKey));
|
setSecretKey(stripeKeys.get('stripe_secret_key'));
|
||||||
}).catch(error => console.error(error));
|
}).catch(error => console.error(error));
|
||||||
|
|
||||||
// when the component unmounts, mark it as unmounted
|
// when the component unmounts, mark it as unmounted
|
||||||
|
@ -5,7 +5,6 @@ import { User } from '../../models/user';
|
|||||||
import { UserPack } from '../../models/user-pack';
|
import { UserPack } from '../../models/user-pack';
|
||||||
import UserPackAPI from '../../api/user-pack';
|
import UserPackAPI from '../../api/user-pack';
|
||||||
import SettingAPI from '../../api/setting';
|
import SettingAPI from '../../api/setting';
|
||||||
import { SettingName } from '../../models/setting';
|
|
||||||
import { FabButton } from '../base/fab-button';
|
import { FabButton } from '../base/fab-button';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ProposePacksModal } from './propose-packs-modal';
|
import { ProposePacksModal } from './propose-packs-modal';
|
||||||
@ -44,10 +43,10 @@ const PacksSummary: React.FC<PacksSummaryProps> = ({ item, itemType, customer, o
|
|||||||
const [isPackOnlyForSubscription, setIsPackOnlyForSubscription] = useState<boolean>(true);
|
const [isPackOnlyForSubscription, setIsPackOnlyForSubscription] = useState<boolean>(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
SettingAPI.get(SettingName.RenewPackThreshold)
|
SettingAPI.get('renew_pack_threshold')
|
||||||
.then(data => setThreshold(parseFloat(data.value)))
|
.then(data => setThreshold(parseFloat(data.value)))
|
||||||
.catch(error => onError(error));
|
.catch(error => onError(error));
|
||||||
SettingAPI.get(SettingName.PackOnlyForSubscription)
|
SettingAPI.get('pack_only_for_subscription')
|
||||||
.then(data => setIsPackOnlyForSubscription(data.value === 'true'))
|
.then(data => setIsPackOnlyForSubscription(data.value === 'true'))
|
||||||
.catch(error => onError(error));
|
.catch(error => onError(error));
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -6,7 +6,7 @@ import { Loader } from '../base/loader';
|
|||||||
import { react2angular } from 'react2angular';
|
import { react2angular } from 'react2angular';
|
||||||
import { IApplication } from '../../models/application';
|
import { IApplication } from '../../models/application';
|
||||||
import SettingAPI from '../../api/setting';
|
import SettingAPI from '../../api/setting';
|
||||||
import { SettingName } from '../../models/setting';
|
import { SettingName, titleSettings } from '../../models/setting';
|
||||||
import UserLib from '../../lib/user';
|
import UserLib from '../../lib/user';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
@ -27,7 +27,7 @@ export const CompletionHeaderInfo: React.FC<CompletionHeaderInfoProps> = ({ user
|
|||||||
const userLib = new UserLib(user);
|
const userLib = new UserLib(user);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
SettingAPI.query([SettingName.NameGenre, SettingName.FablabName]).then(setSettings).catch(onError);
|
SettingAPI.query(titleSettings).then(setSettings).catch(onError);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -39,8 +39,8 @@ export const CompletionHeaderInfo: React.FC<CompletionHeaderInfoProps> = ({ user
|
|||||||
<p className="intro">
|
<p className="intro">
|
||||||
<span>
|
<span>
|
||||||
{t('app.logged.profile_completion.completion_header_info.sso_intro', {
|
{t('app.logged.profile_completion.completion_header_info.sso_intro', {
|
||||||
GENDER: settings?.get(SettingName.NameGenre),
|
GENDER: settings?.get('name_genre'),
|
||||||
NAME: settings?.get(SettingName.FablabName)
|
NAME: settings?.get('fablab_name')
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
<span className="provider-name">
|
<span className="provider-name">
|
||||||
|
@ -36,7 +36,7 @@ export const UserValidationSetting: React.FC<UserValidationSettingProps> = ({ on
|
|||||||
const updateSetting = (name: SettingName, value: string) => {
|
const updateSetting = (name: SettingName, value: string) => {
|
||||||
SettingAPI.update(name, value)
|
SettingAPI.update(name, value)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (name === SettingName.UserValidationRequired) {
|
if (name === 'user_validation_required') {
|
||||||
onSuccess(t('app.admin.settings.account.user_validation_setting.customization_of_SETTING_successfully_saved', {
|
onSuccess(t('app.admin.settings.account.user_validation_setting.customization_of_SETTING_successfully_saved', {
|
||||||
SETTING: t(`app.admin.settings.account.${name}`) // eslint-disable-line fabmanager/scoped-translation
|
SETTING: t(`app.admin.settings.account.${name}`) // eslint-disable-line fabmanager/scoped-translation
|
||||||
}));
|
}));
|
||||||
@ -45,7 +45,7 @@ export const UserValidationSetting: React.FC<UserValidationSettingProps> = ({ on
|
|||||||
if (err.status === 304) return;
|
if (err.status === 304) return;
|
||||||
|
|
||||||
if (err.status === 423) {
|
if (err.status === 423) {
|
||||||
if (name === SettingName.UserValidationRequired) {
|
if (name === 'user_validation_required') {
|
||||||
onError(t('app.admin.settings.account.user_validation_setting.error_SETTING_locked', {
|
onError(t('app.admin.settings.account.user_validation_setting.error_SETTING_locked', {
|
||||||
SETTING: t(`app.admin.settings.account.${name}`) // eslint-disable-line fabmanager/scoped-translation
|
SETTING: t(`app.admin.settings.account.${name}`) // eslint-disable-line fabmanager/scoped-translation
|
||||||
}));
|
}));
|
||||||
@ -62,19 +62,19 @@ export const UserValidationSetting: React.FC<UserValidationSettingProps> = ({ on
|
|||||||
* Callback triggered when the 'save' button is clicked.
|
* Callback triggered when the 'save' button is clicked.
|
||||||
*/
|
*/
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
updateSetting(SettingName.UserValidationRequired, userValidationRequired);
|
updateSetting('user_validation_required', userValidationRequired);
|
||||||
if (userValidationRequiredList !== null) {
|
if (userValidationRequiredList !== null) {
|
||||||
if (userValidationRequired === 'true') {
|
if (userValidationRequired === 'true') {
|
||||||
updateSetting(SettingName.UserValidationRequiredList, userValidationRequiredList);
|
updateSetting('user_validation_required_list', userValidationRequiredList);
|
||||||
} else {
|
} else {
|
||||||
updateSetting(SettingName.UserValidationRequiredList, null);
|
updateSetting('user_validation_required_list', null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="user-validation-setting">
|
<div className="user-validation-setting">
|
||||||
<BooleanSetting name={SettingName.UserValidationRequired}
|
<BooleanSetting name={'user_validation_required'}
|
||||||
label={t('app.admin.settings.account.user_validation_setting.user_validation_required_option_label')}
|
label={t('app.admin.settings.account.user_validation_setting.user_validation_required_option_label')}
|
||||||
hideSave={true}
|
hideSave={true}
|
||||||
onChange={setUserValidationRequired}
|
onChange={setUserValidationRequired}
|
||||||
@ -90,7 +90,7 @@ export const UserValidationSetting: React.FC<UserValidationSettingProps> = ({ on
|
|||||||
<FabAlert level="warning">
|
<FabAlert level="warning">
|
||||||
{t('app.admin.settings.account.user_validation_setting.user_validation_required_list_other_info')}
|
{t('app.admin.settings.account.user_validation_setting.user_validation_required_list_other_info')}
|
||||||
</FabAlert>
|
</FabAlert>
|
||||||
<CheckListSetting name={SettingName.UserValidationRequiredList}
|
<CheckListSetting name={'user_validation_required_list'}
|
||||||
label=""
|
label=""
|
||||||
availableOptions={userValidationRequiredOptions}
|
availableOptions={userValidationRequiredOptions}
|
||||||
defaultValue={userValidationRequiredListDefault.join(',')}
|
defaultValue={userValidationRequiredListDefault.join(',')}
|
||||||
|
@ -25,7 +25,7 @@ interface FabSocialsProps {
|
|||||||
*/
|
*/
|
||||||
export const FabSocials: React.FC<FabSocialsProps> = ({ show = false, onError, onSuccess }) => {
|
export const FabSocials: React.FC<FabSocialsProps> = ({ show = false, onError, onSuccess }) => {
|
||||||
const { t } = useTranslation('shared');
|
const { t } = useTranslation('shared');
|
||||||
// regular expression to validate the the input fields
|
// regular expression to validate the input fields
|
||||||
const urlRegex = /^(https?:\/\/)([\da-z.-]+)\.([-a-z\d.]{2,30})([/\w .-]*)*\/?$/;
|
const urlRegex = /^(https?:\/\/)([\da-z.-]+)\.([-a-z\d.]{2,30})([/\w .-]*)*\/?$/;
|
||||||
|
|
||||||
const { handleSubmit, register, setValue, formState } = useForm();
|
const { handleSubmit, register, setValue, formState } = useForm();
|
||||||
|
@ -23,7 +23,7 @@ export const PasswordInput = <TFieldValues extends FieldValues>({ register, curr
|
|||||||
rules={{
|
rules={{
|
||||||
required: true,
|
required: true,
|
||||||
validate: (value: string) => {
|
validate: (value: string) => {
|
||||||
if (value.length < 8) {
|
if (value.length < 12) {
|
||||||
return t('app.shared.password_input.password_too_short') as string;
|
return t('app.shared.password_input.password_too_short') as string;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -99,7 +99,7 @@ export const UserProfileForm: React.FC<UserProfileFormProps> = ({ action, size,
|
|||||||
});
|
});
|
||||||
setValue('invoicing_profile_attributes.user_profile_custom_fields_attributes', userProfileCustomFields);
|
setValue('invoicing_profile_attributes.user_profile_custom_fields_attributes', userProfileCustomFields);
|
||||||
}).catch(error => onError(error));
|
}).catch(error => onError(error));
|
||||||
SettingAPI.query([SettingName.PhoneRequired, SettingName.AddressRequired])
|
SettingAPI.query(['phone_required', 'address_required'])
|
||||||
.then(settings => setRequiredFieldsSettings(settings))
|
.then(settings => setRequiredFieldsSettings(settings))
|
||||||
.catch(error => onError(error));
|
.catch(error => onError(error));
|
||||||
}, []);
|
}, []);
|
||||||
@ -219,7 +219,7 @@ export const UserProfileForm: React.FC<UserProfileFormProps> = ({ action, size,
|
|||||||
value: phoneRegex,
|
value: phoneRegex,
|
||||||
message: t('app.shared.user_profile_form.phone_number_invalid')
|
message: t('app.shared.user_profile_form.phone_number_invalid')
|
||||||
},
|
},
|
||||||
required: requiredFieldsSettings.get(SettingName.PhoneRequired) === 'true'
|
required: requiredFieldsSettings.get('phone_required') === 'true'
|
||||||
}}
|
}}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
formState={formState}
|
formState={formState}
|
||||||
@ -232,7 +232,7 @@ export const UserProfileForm: React.FC<UserProfileFormProps> = ({ action, size,
|
|||||||
<FormInput id="invoicing_profile_attributes.address_attributes.address"
|
<FormInput id="invoicing_profile_attributes.address_attributes.address"
|
||||||
register={register}
|
register={register}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
rules={{ required: requiredFieldsSettings.get(SettingName.AddressRequired) === 'true' }}
|
rules={{ required: requiredFieldsSettings.get('address_required') === 'true' }}
|
||||||
label={t('app.shared.user_profile_form.address')} />
|
label={t('app.shared.user_profile_form.address')} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -70,20 +70,23 @@ Application.Controllers.controller('GroupsController', ['$scope', 'groupsPromise
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the group at the specified index
|
* Deletes the group at the specified index
|
||||||
* @param index {number} group index in the $scope.groups array
|
* @param groupId {number} group id to delete
|
||||||
*/
|
*/
|
||||||
$scope.removeGroup = index =>
|
$scope.removeGroup = (groupId) => {
|
||||||
Group.delete({ id: $scope.groups[index].id }, function (resp) {
|
Group.delete({ id: groupId }, function (resp) {
|
||||||
growl.success(_t('app.admin.members.group_form.group_successfully_deleted'));
|
growl.success(_t('app.admin.members.group_form.group_successfully_deleted'));
|
||||||
return $scope.groups.splice(index, 1);
|
const index = $scope.groups.findIndex(e => e.id === groupId);
|
||||||
|
$scope.groups.splice(index, 1);
|
||||||
}
|
}
|
||||||
, () => growl.error(_t('app.admin.members.group_form.unable_to_delete_group_because_some_users_and_or_groups_are_still_linked_to_it')));
|
, () => growl.error(_t('app.admin.members.group_form.unable_to_delete_group_because_some_users_and_or_groups_are_still_linked_to_it')));
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable/disable the group at the specified index
|
* Enable/disable the group at the specified index
|
||||||
* @param index {number} group index in the $scope.groups array
|
* @param groupId {number} id of the group to enable/disable
|
||||||
*/
|
*/
|
||||||
return $scope.toggleDisableGroup = function (index) {
|
return $scope.toggleDisableGroup = function (groupId) {
|
||||||
|
const index = $scope.groups.findIndex(e => e.id === groupId);
|
||||||
const group = $scope.groups[index];
|
const group = $scope.groups[index];
|
||||||
if (!group.disabled && (group.users > 0)) {
|
if (!group.disabled && (group.users > 0)) {
|
||||||
return growl.error(_t('app.admin.members.group_form.unable_to_disable_group_with_users', { USERS: group.users }));
|
return growl.error(_t('app.admin.members.group_form.unable_to_disable_group_with_users', { USERS: group.users }));
|
||||||
|
@ -1,144 +1,242 @@
|
|||||||
import { HistoryValue } from './history-value';
|
import { HistoryValue } from './history-value';
|
||||||
import { TDateISO } from '../typings/date-iso';
|
import { TDateISO } from '../typings/date-iso';
|
||||||
|
|
||||||
export enum SettingName {
|
export const homePageSettings = [
|
||||||
AboutTitle = 'about_title',
|
'twitter_name',
|
||||||
AboutBody = 'about_body',
|
'home_blogpost',
|
||||||
AboutContacts = 'about_contacts',
|
'home_content',
|
||||||
PrivacyDraft = 'privacy_draft',
|
'home_css',
|
||||||
PrivacyBody = 'privacy_body',
|
'upcoming_events_shown'
|
||||||
PrivacyDpo = 'privacy_dpo',
|
];
|
||||||
TwitterName = 'twitter_name',
|
|
||||||
HomeBlogpost = 'home_blogpost',
|
export const privacyPolicySettings = [
|
||||||
MachineExplicationsAlert = 'machine_explications_alert',
|
'privacy_draft',
|
||||||
TrainingExplicationsAlert = 'training_explications_alert',
|
'privacy_body',
|
||||||
TrainingInformationMessage = 'training_information_message',
|
'privacy_dpo'
|
||||||
SubscriptionExplicationsAlert = 'subscription_explications_alert',
|
];
|
||||||
InvoiceLogo = 'invoice_logo',
|
|
||||||
InvoiceReference = 'invoice_reference',
|
export const aboutPageSettings = [
|
||||||
InvoiceCodeActive = 'invoice_code-active',
|
'about_title',
|
||||||
InvoiceCodeValue = 'invoice_code-value',
|
'about_body',
|
||||||
InvoiceOrderNb = 'invoice_order-nb',
|
'about_contacts',
|
||||||
InvoiceVATActive = 'invoice_VAT-active',
|
'link_name'
|
||||||
InvoiceVATRate = 'invoice_VAT-rate',
|
];
|
||||||
InvoiceVATRateMachine = 'invoice_VAT-rate_Machine',
|
|
||||||
InvoiceVATRateTraining = 'invoice_VAT-rate_Training',
|
export const socialNetworksSettings = [
|
||||||
InvoiceVATRateSpace = 'invoice_VAT-rate_Space',
|
'facebook',
|
||||||
InvoiceVATRateEvent = 'invoice_VAT-rate_Event',
|
'twitter',
|
||||||
InvoiceVATRateSubscription = 'invoice_VAT-rate_Subscription',
|
'viadeo',
|
||||||
InvoiceText = 'invoice_text',
|
'linkedin',
|
||||||
InvoiceLegals = 'invoice_legals',
|
'instagram',
|
||||||
BookingWindowStart = 'booking_window_start',
|
'youtube',
|
||||||
BookingWindowEnd = 'booking_window_end',
|
'vimeo',
|
||||||
BookingMoveEnable = 'booking_move_enable',
|
'dailymotion',
|
||||||
BookingMoveDelay = 'booking_move_delay',
|
'github',
|
||||||
BookingCancelEnable = 'booking_cancel_enable',
|
'echosciences',
|
||||||
BookingCancelDelay = 'booking_cancel_delay',
|
'pinterest',
|
||||||
MainColor = 'main_color',
|
'lastfm',
|
||||||
SecondaryColor = 'secondary_color',
|
'flickr'
|
||||||
FablabName = 'fablab_name',
|
];
|
||||||
NameGenre = 'name_genre',
|
|
||||||
ReminderEnable = 'reminder_enable',
|
export const messagesSettings = [
|
||||||
ReminderDelay = 'reminder_delay',
|
'machine_explications_alert',
|
||||||
EventExplicationsAlert = 'event_explications_alert',
|
'training_explications_alert',
|
||||||
SpaceExplicationsAlert = 'space_explications_alert',
|
'training_information_message',
|
||||||
VisibilityYearly = 'visibility_yearly',
|
'subscription_explications_alert',
|
||||||
VisibilityOthers = 'visibility_others',
|
'event_explications_alert',
|
||||||
DisplayNameEnable = 'display_name_enable',
|
'space_explications_alert'
|
||||||
MachinesSortBy = 'machines_sort_by',
|
];
|
||||||
AccountingJournalCode = 'accounting_journal_code',
|
|
||||||
AccountingCardClientCode = 'accounting_card_client_code',
|
export const invoicesSettings = [
|
||||||
AccountingCardClientLabel = 'accounting_card_client_label',
|
'invoice_logo',
|
||||||
AccountingWalletClientCode = 'accounting_wallet_client_code',
|
'invoice_reference',
|
||||||
AccountingWalletClientLabel = 'accounting_wallet_client_label',
|
'invoice_code-active',
|
||||||
AccountingOtherClientCode = 'accounting_other_client_code',
|
'invoice_code-value',
|
||||||
AccountingOtherClientLabel = 'accounting_other_client_label',
|
'invoice_order-nb',
|
||||||
AccountingWalletCode = 'accounting_wallet_code',
|
'invoice_VAT-active',
|
||||||
AccountingWalletLabel = 'accounting_wallet_label',
|
'invoice_VAT-rate',
|
||||||
AccountingVATCode = 'accounting_VAT_code',
|
'invoice_VAT-rate_Machine',
|
||||||
AccountingVATLabel = 'accounting_VAT_label',
|
'invoice_VAT-rate_Training',
|
||||||
AccountingSubscriptionCode = 'accounting_subscription_code',
|
'invoice_VAT-rate_Space',
|
||||||
AccountingSubscriptionLabel = 'accounting_subscription_label',
|
'invoice_VAT-rate_Event',
|
||||||
AccountingMachineCode = 'accounting_Machine_code',
|
'invoice_VAT-rate_Subscription',
|
||||||
AccountingMachineLabel = 'accounting_Machine_label',
|
'invoice_text',
|
||||||
AccountingTrainingCode = 'accounting_Training_code',
|
'invoice_legals',
|
||||||
AccountingTrainingLabel = 'accounting_Training_label',
|
'invoice_prefix',
|
||||||
AccountingEventCode = 'accounting_Event_code',
|
'payment_schedule_prefix'
|
||||||
AccountingEventLabel = 'accounting_Event_label',
|
];
|
||||||
AccountingSpaceCode = 'accounting_Space_code',
|
|
||||||
AccountingSpaceLabel = 'accounting_Space_label',
|
export const bookingSettings = [
|
||||||
HubLastVersion = 'hub_last_version',
|
'booking_window_start',
|
||||||
HubPublicKey = 'hub_public_key',
|
'booking_window_end',
|
||||||
FabAnalytics = 'fab_analytics',
|
'booking_move_enable',
|
||||||
LinkName = 'link_name',
|
'booking_move_delay',
|
||||||
HomeContent = 'home_content',
|
'booking_cancel_enable',
|
||||||
HomeCss = 'home_css',
|
'booking_cancel_delay',
|
||||||
Origin = 'origin',
|
'reminder_enable',
|
||||||
Uuid = 'uuid',
|
'reminder_delay',
|
||||||
PhoneRequired = 'phone_required',
|
'visibility_yearly',
|
||||||
TrackingId = 'tracking_id',
|
'visibility_others',
|
||||||
BookOverlappingSlots = 'book_overlapping_slots',
|
'display_name_enable',
|
||||||
SlotDuration = 'slot_duration',
|
'book_overlapping_slots',
|
||||||
EventsInCalendar = 'events_in_calendar',
|
'slot_duration',
|
||||||
SpacesModule = 'spaces_module',
|
'overlapping_categories'
|
||||||
PlansModule = 'plans_module',
|
];
|
||||||
InvoicingModule = 'invoicing_module',
|
|
||||||
FacebookAppId = 'facebook_app_id',
|
export const themeSettings = [
|
||||||
TwitterAnalytics = 'twitter_analytics',
|
'main_color',
|
||||||
RecaptchaSiteKey = 'recaptcha_site_key',
|
'secondary_color'
|
||||||
RecaptchaSecretKey = 'recaptcha_secret_key',
|
];
|
||||||
FeatureTourDisplay = 'feature_tour_display',
|
|
||||||
EmailFrom = 'email_from',
|
export const titleSettings = [
|
||||||
DisqusShortname = 'disqus_shortname',
|
'fablab_name',
|
||||||
AllowedCadExtensions = 'allowed_cad_extensions',
|
'name_genre'
|
||||||
AllowedCadMimeTypes = 'allowed_cad_mime_types',
|
];
|
||||||
OpenlabAppId = 'openlab_app_id',
|
|
||||||
OpenlabAppSecret = 'openlab_app_secret',
|
export const accountingSettings = [
|
||||||
OpenlabDefault = 'openlab_default',
|
'accounting_journal_code',
|
||||||
OnlinePaymentModule = 'online_payment_module',
|
'accounting_card_client_code',
|
||||||
StripePublicKey = 'stripe_public_key',
|
'accounting_card_client_label',
|
||||||
StripeSecretKey = 'stripe_secret_key',
|
'accounting_wallet_client_code',
|
||||||
StripeCurrency = 'stripe_currency',
|
'accounting_wallet_client_label',
|
||||||
InvoicePrefix = 'invoice_prefix',
|
'accounting_other_client_code',
|
||||||
ConfirmationRequired = 'confirmation_required',
|
'accounting_other_client_label',
|
||||||
WalletModule = 'wallet_module',
|
'accounting_wallet_code',
|
||||||
StatisticsModule = 'statistics_module',
|
'accounting_wallet_label',
|
||||||
UpcomingEventsShown = 'upcoming_events_shown',
|
'accounting_VAT_code',
|
||||||
PaymentSchedulePrefix = 'payment_schedule_prefix',
|
'accounting_VAT_label',
|
||||||
TrainingsModule = 'trainings_module',
|
'accounting_subscription_code',
|
||||||
AddressRequired = 'address_required',
|
'accounting_subscription_label',
|
||||||
PaymentGateway = 'payment_gateway',
|
'accounting_Machine_code',
|
||||||
PayZenUsername = 'payzen_username',
|
'accounting_Machine_label',
|
||||||
PayZenPassword = 'payzen_password',
|
'accounting_Training_code',
|
||||||
PayZenEndpoint = 'payzen_endpoint',
|
'accounting_Training_label',
|
||||||
PayZenPublicKey = 'payzen_public_key',
|
'accounting_Event_code',
|
||||||
PayZenHmacKey = 'payzen_hmac',
|
'accounting_Event_label',
|
||||||
PayZenCurrency = 'payzen_currency',
|
'accounting_Space_code',
|
||||||
PublicAgendaModule = 'public_agenda_module',
|
'accounting_Space_label'
|
||||||
RenewPackThreshold = 'renew_pack_threshold',
|
];
|
||||||
PackOnlyForSubscription = 'pack_only_for_subscription',
|
|
||||||
OverlappingCategories = 'overlapping_categories',
|
export const modulesSettings = [
|
||||||
ExtendedPricesInSameDay = 'extended_prices_in_same_day',
|
'spaces_module',
|
||||||
PublicRegistrations = 'public_registrations',
|
'plans_module',
|
||||||
SocialsFacebook = 'facebook',
|
'wallet_module',
|
||||||
SocialsTwitter = 'twitter',
|
'statistics_module',
|
||||||
SocialsViadeo = 'viadeo',
|
'trainings_module',
|
||||||
SocialsLinkedin = 'linkedin',
|
'machines_module',
|
||||||
SocialsInstagram = 'instagram',
|
'online_payment_module',
|
||||||
SocialsYoutube = 'youtube',
|
'public_agenda_module',
|
||||||
SocialsVimeo = 'vimeo',
|
'invoicing_module'
|
||||||
SocialsDailymotion = 'dailymotion',
|
];
|
||||||
SocialsGithub = 'github',
|
|
||||||
SocialsEchosciences = 'echosciences',
|
export const stripeSettings = [
|
||||||
SocialsPinterest = 'pinterest',
|
'stripe_public_key',
|
||||||
SocialsLastfm = 'lastfm',
|
'stripe_secret_key',
|
||||||
SocialsFlickr = 'flickr',
|
'stripe_currency'
|
||||||
MachinesModule = 'machines_module',
|
];
|
||||||
UserChangeGroup = 'user_change_group',
|
|
||||||
UserValidationRequired = 'user_validation_required',
|
export const payzenSettings = [
|
||||||
UserValidationRequiredList = 'user_validation_required_list',
|
'payzen_username',
|
||||||
ShowUsernameInAdminList = 'show_username_in_admin_list'
|
'payzen_password',
|
||||||
}
|
'payzen_endpoint',
|
||||||
|
'payzen_public_key',
|
||||||
|
'payzen_hmac',
|
||||||
|
'payzen_currency'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const openLabSettings = [
|
||||||
|
'openlab_app_id',
|
||||||
|
'openlab_app_secret',
|
||||||
|
'openlab_default'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const accountSettings = [
|
||||||
|
'phone_required',
|
||||||
|
'confirmation_required',
|
||||||
|
'address_required',
|
||||||
|
'user_change_group',
|
||||||
|
'user_validation_required',
|
||||||
|
'user_validation_required_list'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const analyticsSettings = [
|
||||||
|
'tracking_id',
|
||||||
|
'facebook_app_id',
|
||||||
|
'twitter_analytics'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const fabHubSettings = [
|
||||||
|
'hub_last_version',
|
||||||
|
'hub_public_key',
|
||||||
|
'fab_analytics',
|
||||||
|
'origin',
|
||||||
|
'uuid'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const projectsSettings = [
|
||||||
|
'allowed_cad_extensions',
|
||||||
|
'allowed_cad_mime_types',
|
||||||
|
'disqus_shortname'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const prepaidPacksSettings = [
|
||||||
|
'renew_pack_threshold',
|
||||||
|
'pack_only_for_subscription'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const registrationSettings = [
|
||||||
|
'public_registrations',
|
||||||
|
'recaptcha_site_key',
|
||||||
|
'recaptcha_secret_key'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const adminSettings = [
|
||||||
|
'feature_tour_display',
|
||||||
|
'show_username_in_admin_list'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const pricingSettings = [
|
||||||
|
'extended_prices_in_same_day'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const poymentSettings = [
|
||||||
|
'payment_gateway'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const displaySettings = [
|
||||||
|
'machines_sort_by',
|
||||||
|
'events_in_calendar',
|
||||||
|
'email_from'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const allSettings = [
|
||||||
|
...homePageSettings,
|
||||||
|
...privacyPolicySettings,
|
||||||
|
...aboutPageSettings,
|
||||||
|
...socialNetworksSettings,
|
||||||
|
...messagesSettings,
|
||||||
|
...invoicesSettings,
|
||||||
|
...bookingSettings,
|
||||||
|
...themeSettings,
|
||||||
|
...titleSettings,
|
||||||
|
...accountingSettings,
|
||||||
|
...modulesSettings,
|
||||||
|
...stripeSettings,
|
||||||
|
...payzenSettings,
|
||||||
|
...openLabSettings,
|
||||||
|
...accountSettings,
|
||||||
|
...analyticsSettings,
|
||||||
|
...fabHubSettings,
|
||||||
|
...projectsSettings,
|
||||||
|
...prepaidPacksSettings,
|
||||||
|
...registrationSettings,
|
||||||
|
...adminSettings,
|
||||||
|
...pricingSettings,
|
||||||
|
...poymentSettings,
|
||||||
|
...displaySettings
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type SettingName = typeof allSettings[number];
|
||||||
|
|
||||||
export type SettingValue = string|boolean|number;
|
export type SettingValue = string|boolean|number;
|
||||||
|
|
||||||
@ -153,7 +251,7 @@ export interface Setting {
|
|||||||
export interface SettingError {
|
export interface SettingError {
|
||||||
error: string,
|
error: string,
|
||||||
id: number,
|
id: number,
|
||||||
name: string
|
name: SettingName
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SettingBulkResult {
|
export interface SettingBulkResult {
|
||||||
|
@ -588,10 +588,18 @@
|
|||||||
.checkbox-group {
|
.checkbox-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
input[type=checkbox] {
|
input[type=checkbox] {
|
||||||
font-size: 16px;
|
flex-shrink: 0;
|
||||||
width: 2em;
|
width: 2em;
|
||||||
|
height: 2rem;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
.signup-form {
|
.signup-form {
|
||||||
.names-row {
|
.names-row {
|
||||||
input.form-control {
|
& > div {
|
||||||
width: 89%;
|
display: flex;
|
||||||
display: inline-block;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,11 +41,11 @@
|
|||||||
<button class="btn btn-default" ng-click="rowform.$show()">
|
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'app.shared.buttons.edit' }}</span>
|
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'app.shared.buttons.edit' }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-default" ng-click="toggleDisableGroup($index)">
|
<button class="btn btn-default" ng-click="toggleDisableGroup(group.id)">
|
||||||
<span ng-hide="group.disabled"><i class="fa fa-eye-slash"></i> <span translate>{{ 'app.admin.members.group_form.disable' }}</span></span>
|
<span ng-hide="group.disabled"><i class="fa fa-eye-slash"></i> <span translate>{{ 'app.admin.members.group_form.disable' }}</span></span>
|
||||||
<span ng-show="group.disabled"><i class="fa fa-eye"></i> <span translate>{{ 'app.admin.members.group_form.enable' }}</span></span>
|
<span ng-show="group.disabled"><i class="fa fa-eye"></i> <span translate>{{ 'app.admin.members.group_form.enable' }}</span></span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-danger" ng-click="removeGroup($index)">
|
<button class="btn btn-danger" ng-click="removeGroup(group.id)">
|
||||||
<i class="fa fa-trash-o"></i>
|
<i class="fa fa-trash-o"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,566 +0,0 @@
|
|||||||
<uib-alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</uib-alert>
|
|
||||||
|
|
||||||
<input name="_method" type="hidden" ng-value="method">
|
|
||||||
<input name="user[profile_attributes][id]" type="hidden" ng-value="user.profile_attributes.id">
|
|
||||||
<input name="user[invoicing_profile_attributes][id]" type="hidden" ng-value="user.invoicing_profile.id">
|
|
||||||
<input name="user[statistic_profile_attributes][id]" type="hidden" ng-value="user.statistic_profile.id">
|
|
||||||
|
|
||||||
<div class="row m-t">
|
|
||||||
<div class="col-sm-3 col-sm-offset-1">
|
|
||||||
<div class="form-group m-t-lg">
|
|
||||||
<div class="fileinput text-center" data-provides="fileinput" ng-class="fileinputClass(user.profile_attributes.user_avatar_attributes.attachment_url)">
|
|
||||||
<div class="fileinput-new thumbnail rounded thumb-128-wrapper" style="width: 140px; height: 140px;">
|
|
||||||
<img src="../../images/no_avatar.png" class="img-circle">
|
|
||||||
</div>
|
|
||||||
<div class="fileinput-preview fileinput-exists thumbnail rounded thumb-128-wrapper" data-trigger="fileinput" style="width: 140px; height: 140px; line-height: 140px;">
|
|
||||||
<img ng-src="{{ user.profile_attributes.user_avatar_attributes.attachment_url }}" />
|
|
||||||
</div>
|
|
||||||
<div class="m-t-sm">
|
|
||||||
<input type="hidden" name="user[profile_attributes][user_avatar_attributes][id]" ng-value="user.profile_attributes.user_avatar_attributes.id">
|
|
||||||
<input type="hidden" name="user[profile_attributes][user_avatar_attributes][_destroy]" ng-value="true" ng-if="user.profile_attributes.user_avatar._destory">
|
|
||||||
<span class="btn btn-default btn-file"
|
|
||||||
ng-click="user.profile_attributes.user_avatar_attributes._destory = false"
|
|
||||||
ng-hide="preventField['profile.avatar'] && user.profile_attributes.user_avatar_attributes.attachment_url && !userForm['user[profile_attributes][user_avatar_attributes]'].$dirty">
|
|
||||||
<span class="fileinput-new" translate>{{ 'app.shared.user.add_an_avatar' }}</span>
|
|
||||||
<span class="fileinput-exists" translate>{{ 'app.shared.buttons.change' }}</span>
|
|
||||||
<input type="file" name="user[profile_attributes][user_avatar_attributes][attachment]" accept="image/jpeg,image/gif,image/png">
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<button class="btn btn-danger fileinput-exists"
|
|
||||||
data-dismiss="fileinput"
|
|
||||||
ng-click="user.profile_attributes.user_avatar_attributes._destory = true"
|
|
||||||
ng-hide="preventField['profile.avatar'] && user.profile_attributes.user_avatar.attachment_url && !userForm['user[profile_attributes][user_avatar_attributes]'].$dirty">
|
|
||||||
<i class="fa fa-trash-o"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-offset-1 col-sm-6">
|
|
||||||
|
|
||||||
<div class="form-group" ng-class="{'has-error': userForm['user[statistic_profile_attributes][gender]'].$dirty && userForm['user[statistic_profile_attributes][gender]'].$invalid}">
|
|
||||||
<label class="checkbox-inline btn btn-default">
|
|
||||||
<input type="radio"
|
|
||||||
name="user[statistic_profile_attributes][gender]"
|
|
||||||
ng-model="user.statistic_profile_attributes.gender"
|
|
||||||
value="true"
|
|
||||||
ng-disabled="preventField['profile.gender'] && user.statistic_profile_attributes.gender && !userForm['user[statistic_profile_attributes][gender]'].$dirty"
|
|
||||||
required/>
|
|
||||||
<i class="fa fa-male m-l-sm"></i> {{ 'app.shared.user.man' | translate }}
|
|
||||||
</label>
|
|
||||||
<label class="checkbox-inline btn btn-default">
|
|
||||||
<input type="radio"
|
|
||||||
name="user[statistic_profile_attributes][gender]"
|
|
||||||
ng-model="user.statistic_profile_attributes.gender"
|
|
||||||
value="false"
|
|
||||||
ng-disabled="preventField['profile.gender'] && user.statistic_profile_attributes.gender && !userForm['user[statistic_profile_attributes][gender]'].$dirty"/>
|
|
||||||
<i class="fa fa-female m-l-sm"></i> {{ 'app.shared.user.woman' | translate }}
|
|
||||||
</label>
|
|
||||||
<span class="exponent m-l-xs help-cursor" title="{{ 'app.shared.user.used_for_statistics' | translate }}"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
|
||||||
|
|
||||||
<span class="help-block" ng-show="userForm['user[statistic_profile_attributes][gender]'].$dirty && userForm['user[statistic_profile_attributes][gender]'].$error.required" translate>{{ 'app.shared.user.gender_is_required' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group" ng-class="{'has-error': userForm['user[username]'].$dirty && userForm['user[username]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}"><i class="fa fa-user"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
|
||||||
</span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[username]"
|
|
||||||
ng-model="user.username"
|
|
||||||
class="form-control"
|
|
||||||
id="user_username"
|
|
||||||
placeholder="{{ 'app.shared.user.pseudonym' | translate }}"
|
|
||||||
ng-disabled="preventField['user.username'] && user.username && !userForm['user[username]'].$dirty"
|
|
||||||
required/>
|
|
||||||
</div>
|
|
||||||
<span class="help-block" ng-show="userForm['user[username]'].$dirty && userForm['user[username]'].$error.required" translate>{{ 'app.shared.user.pseudonym_is_required' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][last_name]'].$dirty && userForm['user[profile_attributes][last_name]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_invoicing' | translate }}"><i class="fa fa-user"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[profile_attributes][last_name]"
|
|
||||||
ng-model="user.profile_attributes.last_name"
|
|
||||||
class="form-control"
|
|
||||||
id="user_last_name"
|
|
||||||
placeholder="{{ 'app.shared.user.surname' | translate }}"
|
|
||||||
ng-disabled="preventField['profile.last_name'] && user.profile_attributes.last_name && !userForm['user[profile_attributes][last_name]'].$dirty"
|
|
||||||
required/>
|
|
||||||
</div>
|
|
||||||
<span class="help-block" ng-show="userForm['user[profile_attributes][last_name]'].$dirty && userForm['user[profile_attributes][last_name]'].$error.required" translate>{{ 'app.shared.user.surname_is_required' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][first_name]'].$dirty && userForm['user[profile_attributes][first_name]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_invoicing' | translate }}"><i class="fa fa-user"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[profile_attributes][first_name]"
|
|
||||||
ng-model="user.profile_attributes.first_name"
|
|
||||||
class="form-control"
|
|
||||||
id="user_first_name"
|
|
||||||
placeholder="{{ 'app.shared.user.first_name' | translate }}"
|
|
||||||
ng-disabled="preventField['profile.first_name'] && user.profile_attributes.first_name && !userForm['user[profile_attributes][first_name]'].$dirty"
|
|
||||||
required/>
|
|
||||||
</div>
|
|
||||||
<span class="help-block" ng-show="userForm['user[profile_attributes][first_name]'].$dirty && userForm['user[profile_attributes][first_name]'].$error.required" translate>{{ 'app.shared.user.first_name_is_required' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-class="{'has-error': userForm['user[email]'].$dirty && userForm['user[email]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_invoicing' | translate }}"><i class="fa fa-envelope"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
|
||||||
<input type="email"
|
|
||||||
name="user[email]"
|
|
||||||
ng-model="user.email"
|
|
||||||
class="form-control"
|
|
||||||
id="user_email"
|
|
||||||
placeholder="{{ 'app.shared.user.email_address' | translate }}"
|
|
||||||
ng-disabled="preventField['user.email'] && user.email && !userForm['user[email]'].$dirty"
|
|
||||||
required/>
|
|
||||||
</div>
|
|
||||||
<span class="help-block" ng-show="userForm['user[email]'].$dirty && userForm['user[email]'].$error.required" translate>{{ 'app.shared.user.email_address_is_required' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-hide="preventPassword">
|
|
||||||
<button class="btn btn-warning btn-block"
|
|
||||||
ng-click="password.change = !password.change; $event.stopPropagation(); $event.preventDefault()"
|
|
||||||
translate>{{ 'app.shared.user.change_password' }}</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group" ng-class="{'has-error': userForm['user[password]'].$dirty && userForm['user[password]'].$invalid}" ng-if="password.change">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon"><i class="fa fa-key"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
|
||||||
<input type="password"
|
|
||||||
name="user[password]"
|
|
||||||
ng-model="user.password"
|
|
||||||
class="form-control"
|
|
||||||
id="user_password"
|
|
||||||
placeholder="{{ 'app.shared.user.new_password' | translate }}"
|
|
||||||
ng-minlength="12"
|
|
||||||
required/>
|
|
||||||
</div>
|
|
||||||
<span class="help-block" ng-show="userForm['user[password]'].$dirty && userForm['user[password]'].$error.required" translate>{{ 'app.shared.user.password_is_required' }}</span>
|
|
||||||
<span class="help-block" ng-show="userForm['user[password]'].$dirty && userForm['user[password]'].$error.minlength" translate>{{ 'app.shared.user.password_is_too_short' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-class="{'has-error': userForm['user[password_confirmation]'].$dirty && userForm['user[password_confirmation]'].$invalid}" ng-if="password.change">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon"><i class="fa fa-key"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
|
||||||
<input type="password"
|
|
||||||
name="user[password_confirmation]"
|
|
||||||
ng-model="user.password_confirmation"
|
|
||||||
class="form-control"
|
|
||||||
id="user_password_confirmation"
|
|
||||||
placeholder="{{ 'app.shared.user.confirmation_of_new_password' | translate }}"
|
|
||||||
ng-minlength="12"
|
|
||||||
required
|
|
||||||
match="user.password"/>
|
|
||||||
</div>
|
|
||||||
<span class="help-block" ng-show="userForm['user[password_confirmation]'].$dirty && userForm['user[password_confirmation]'].$error.required" translate>{{ 'app.shared.user.confirmation_of_password_is_required' }}</span>
|
|
||||||
<span class="help-block" ng-show="userForm['user[password_confirmation]'].$dirty && userForm['user[password_confirmation]'].$error.minlength" translate>{{ 'app.shared.user.confirmation_of_password_is_too_short' }}</span>
|
|
||||||
<span class="help-block" ng-show="userForm['user[password_confirmation]'].$error.match" translate>{{ 'app.shared.user.confirmation_mismatch_with_password' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-if="user.invoicing_profile_attributes.organization" ng-class="{'has-error': userForm['user[invoicing_profile_attributes][organization_attributes][name]'].$dirty && userForm['user[invoicing_profile_attributes][organization_attributes][name]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_invoicing' | translate }}"><i class="fa fa-building-o"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
|
||||||
<input type="hidden"
|
|
||||||
name="user[invoicing_profile_attributes][organization_attributes][id]"
|
|
||||||
ng-value="user.invoicing_profile_attributes.organization.id" />
|
|
||||||
<input type="text"
|
|
||||||
name="user[invoicing_profile_attributes][organization_attributes][name]"
|
|
||||||
ng-model="user.invoicing_profile_attributes.organization.name"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="{{ 'app.shared.user.organization_name' | translate }}"
|
|
||||||
ng-required="user.invoicing_profile.organization"
|
|
||||||
ng-disabled="preventField['profile.organization_name'] && user.invoicing_profile.organization.name && !userForm['user[invoicing_profile_attributes][organization_attributes][name]'].$dirty">
|
|
||||||
</div>
|
|
||||||
<span class="help-block" ng-show="userForm['user[invoicing_][organization_attributes][name]'].$dirty && userForm['user[invoicing_profile_attributes][organization_attributes][name]'].$error.required" translate>{{ 'app.shared.user.organization_name_is_required' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-if="user.invoicing_profile_attributes.organization" ng-class="{'has-error': userForm['user[invoicing_profile_attributes][organization_attributes][address_attributes][address]'].$dirty && userForm['user[invoicing_profile_attributes][organization_attributes][address_attributes][address]'].$invalid}">
|
|
||||||
<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 class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
|
||||||
<input type="hidden"
|
|
||||||
name="user[invoicing_profile_attributes][organization_attributes][address_attributes][id]"
|
|
||||||
ng-value="user.invoicing_profile_attributes.organization.address.id" />
|
|
||||||
<input type="text"
|
|
||||||
name="user[invoicing_profile_attributes][organization_attributes][address_attributes][address]"
|
|
||||||
ng-model="user.invoicing_profile_attributes.organization.address.address"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="{{ 'app.shared.user.organization_address' | translate }}"
|
|
||||||
ng-required="user.invoicing_profile.organization"
|
|
||||||
ng-disabled="preventField['profile.organization_address'] && user.invoicing_profile_attributes.organization.address.address && !userForm['user[invoicing_profile_attributes][organization_attributes][address_attributes][address]'].$dirty">
|
|
||||||
</div>
|
|
||||||
<span class="help-block" ng-show="userForm['user[invoicing_profile_attributes][organization_attributes][address_attributes][address]'].$dirty && userForm['user[invoicing_profile_attributes][organization_attributes][address_attributes][address]'].$error.required" translate>{{ 'app.shared.user.organization_address_is_required' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-class="{'has-error': userForm['user[statistic_profile_attributes][birthday]'].$dirty && userForm['user[statistic_profile_attributes][birthday]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_statistics' | translate }}"><i class="fa fa-calendar-o"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
|
||||||
<input type="text"
|
|
||||||
id="user_birthday"
|
|
||||||
class="form-control"
|
|
||||||
ng-model="user.statistic_profile_attributes.birthday"
|
|
||||||
uib-datepicker-popup="{{datePicker.format}}"
|
|
||||||
datepicker-options="datePicker.options"
|
|
||||||
is-open="datePicker.opened"
|
|
||||||
placeholder="{{ 'app.shared.user.date_of_birth' | translate }}"
|
|
||||||
ng-click="openDatePicker($event)"
|
|
||||||
ng-disabled="preventField['profile.birthday'] && user.statistic_profile_attributes.birthday && !userForm['user[statistic_profile_attributes][birthday]'].$dirty"
|
|
||||||
required/>
|
|
||||||
<input type="hidden"
|
|
||||||
name="user[statistic_profile_attributes][birthday]"
|
|
||||||
value="{{user.statistic_profile_attributes.birthday | toIsoDate}}" />
|
|
||||||
</div>
|
|
||||||
<span class="help-block" ng-show="userForm['user[statistic_profile_attributes][birthday]'].$dirty && userForm['user[statistic_profile_attributes][birthday]'].$error.required" translate>{{ 'app.shared.user.date_of_birth_is_required' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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 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_attributes.address.id" />
|
|
||||||
<input type="text"
|
|
||||||
name="user[invoicing_profile_attributes][address_attributes][address]"
|
|
||||||
ng-model="user.invoicing_profile_attributes.address.address"
|
|
||||||
class="form-control"
|
|
||||||
id="user_address"
|
|
||||||
ng-disabled="preventField['profile.address'] && user.invoicing_profile_attributes.address.address && !userForm['user[invoicing_profile_attributes][address_attributes][address]'].$dirty"
|
|
||||||
placeholder="{{ 'app.shared.user.address' | translate }}"
|
|
||||||
ng-required="addressRequired"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][phone]'].$dirty && userForm['user[profile_attributes][phone]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_reservation' | translate }}"><i class="fa fa-phone"></i> <span class="exponent" ng-show="phoneRequired"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[profile_attributes][phone]"
|
|
||||||
ng-model="user.profile_attributes.phone"
|
|
||||||
class="form-control"
|
|
||||||
id="user_phone"
|
|
||||||
placeholder="{{ 'app.shared.user.phone_number' | translate }}"
|
|
||||||
ng-disabled="preventField['profile.phone'] && user.profile_attributes.phone && !userForm['user[profile_attributes][phone]'].$dirty"
|
|
||||||
ng-required="phoneRequired"/>
|
|
||||||
</div>
|
|
||||||
<span class="help-block" ng-show="userForm['user[profile_attributes][phone]'].$dirty && userForm['user[profile_attributes][phone]'].$error.required" translate>{{ 'app.shared.user.phone_number_is_required' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][website]'].$dirty && userForm['user[profile_attributes][website]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}"><i class="fa fa-globe"></i> </span>
|
|
||||||
<input type="url"
|
|
||||||
name="user[profile_attributes][website]"
|
|
||||||
ng-model="user.profile_attributes.website"
|
|
||||||
class="form-control"
|
|
||||||
id="user_website"
|
|
||||||
ng-pattern="/^https?:\/\//"
|
|
||||||
placeholder="{{ 'app.shared.user.website' | translate }} (http://...)"
|
|
||||||
ng-disabled="preventField['profile.website'] && user.profile_attributes.website && !userForm['user[profile_attributes][website]'].$dirty"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][job]'].$dirty && userForm['user[profile_attributes][job]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}"><i class="fa fa-briefcase"></i> </span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[profile_attributes][job]"
|
|
||||||
ng-model="user.profile_attributes.job"
|
|
||||||
class="form-control"
|
|
||||||
id="user_job"
|
|
||||||
placeholder="{{ 'app.shared.user.job' | translate }}"
|
|
||||||
ng-disabled="preventField['profile.job'] && user.profile_attributes.job && !userForm['user[profile_attributes][job]'].$dirty"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="user_interest" class="help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}" translate>{{ 'app.shared.user.interests' }}</label>
|
|
||||||
<textarea name="user[profile_attributes][interest]"
|
|
||||||
ng-model="user.profile_attributes.interest"
|
|
||||||
rows="5"
|
|
||||||
class="form-control"
|
|
||||||
id="user_interest"
|
|
||||||
placeholder=""
|
|
||||||
ng-disabled="preventField['profile.interest'] && user.profile_attributes.interest && !userForm['user[profile_attributes][interest]'].$dirty"></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="user_software_mastered" class="help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}" translate>{{ 'app.shared.user.CAD_softwares_mastered' }}</label>
|
|
||||||
<textarea name="user[profile_attributes][software_mastered]"
|
|
||||||
ng-model="user.profile_attributes.software_mastered"
|
|
||||||
rows="5"
|
|
||||||
class="form-control"
|
|
||||||
id="user_software_mastered"
|
|
||||||
placeholder=""
|
|
||||||
ng-disabled="preventField['profile.software_mastered'] && user.profile_attributes.software_mastered && !userForm['user[profile_attributes][software_mastered]'].$dirty"></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- allow contact-->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="allowContact" class="help-cursor" title="{{ 'app.shared.user.public_profile' | translate }}" translate>{{ 'app.shared.user.i_authorize_Fablab_users_registered_on_the_site_to_contact_me' }}</label>
|
|
||||||
<input bs-switch
|
|
||||||
ng-model="user.is_allow_contact"
|
|
||||||
id="allowContact"
|
|
||||||
type="checkbox"
|
|
||||||
class="form-control"
|
|
||||||
switch-on-text="{{ 'app.shared.buttons.yes' | translate }}"
|
|
||||||
switch-off-text="{{ 'app.shared.buttons.no' | translate }}"
|
|
||||||
switch-animate="true"/>
|
|
||||||
<input type="hidden" name="user[is_allow_contact]" value="{{user.is_allow_contact}}"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- allow receive newsletter -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="allowNewsletter" translate>{{ 'app.shared.user.i_accept_to_receive_information_from_the_fablab' }}</label>
|
|
||||||
<input bs-switch
|
|
||||||
ng-model="user.is_allow_newsletter"
|
|
||||||
id="allowNewsletter"
|
|
||||||
type="checkbox"
|
|
||||||
class="form-control"
|
|
||||||
switch-on-text="{{ 'app.shared.buttons.yes' | translate }}"
|
|
||||||
switch-off-text="{{ 'app.shared.buttons.no' | translate }}"
|
|
||||||
switch-animate="true" />
|
|
||||||
<input type="hidden" name="user[is_allow_newsletter]" value="{{user.is_allow_newsletter}}"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="social" ng-init="social={}">
|
|
||||||
<div class="form-group" ng-show="social.facebook || user.profile_attributes.facebook" ng-class="{'has-error': userForm['user[profile_attributes][facebook]'].$dirty && userForm['user[profile_attributes][facebook]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}"><i class="fa fa-facebook"></i></span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[profile_attributes][facebook]"
|
|
||||||
ng-model="user.profile_attributes.facebook"
|
|
||||||
class="form-control"
|
|
||||||
id="user_facebook"
|
|
||||||
ng-pattern="/^https?:\/\/.*?facebook/i"
|
|
||||||
placeholder="https://www.facebook.com/..."
|
|
||||||
ng-disabled="preventField['profile.facebook'] && user.profile_attributes.first_name && !userForm['user[profile_attributes][facebook]'].$dirty"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-show="social.twitter || user.profile_attributes.twitter" ng-class="{'has-error': userForm['user[profile_attributes][twitter]'].$dirty && userForm['user[profile_attributes][twitter]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}"><i class="fa fa-twitter"></i></span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[profile_attributes][twitter]"
|
|
||||||
ng-model="user.profile_attributes.twitter"
|
|
||||||
class="form-control"
|
|
||||||
id="user_twitter"
|
|
||||||
ng-pattern="/^https?:\/\/.*?twitter/"
|
|
||||||
placeholder="https://twitter.com/..."
|
|
||||||
ng-disabled="preventField['profile.twitter'] && user.profile_attributes.first_name && !userForm['user[profile_attributes][twitter]'].$dirty"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-show="social.google_plus || user.profile_attributes.google_plus" ng-class="{'has-error': userForm['user[profile_attributes][google_plus]'].$dirty && userForm['user[profile_attributes][google_plus]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}"><i class="fa fa-google-plus"></i></span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[profile_attributes][google_plus]"
|
|
||||||
ng-model="user.profile_attributes.google_plus"
|
|
||||||
class="form-control"
|
|
||||||
id="user_google_plus"
|
|
||||||
ng-pattern="/^https?:\/\/.*?google/"
|
|
||||||
placeholder="https://plus.google.com/+..."
|
|
||||||
ng-disabled="preventField['profile.google_plus'] && user.profile_attributes.first_name && !userForm['user[profile_attributes][google_plus]'].$dirty"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-show="social.viadeo || user.profile_attributes.viadeo" ng-class="{'has-error': userForm['user[profile_attributes][viadeo]'].$dirty && userForm['user[profile_attributes][viadeo]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}"><i class="fa fa-viadeo"></i></span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[profile_attributes][viadeo]"
|
|
||||||
ng-model="user.profile_attributes.viadeo"
|
|
||||||
class="form-control"
|
|
||||||
id="user_viadeo"
|
|
||||||
ng-pattern="/^https?:\/\/.*?viadeo/"
|
|
||||||
placeholder="http://www.viadeo.com/fr/profile/..."
|
|
||||||
ng-disabled="preventField['profile.viadeo'] && user.profile_attributes.first_name && !userForm['user[profile_attributes][viadeo]'].$dirty"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-show="social.linkedin || user.profile_attributes.linkedin" ng-class="{'has-error': userForm['user[profile_attributes][linkedin]'].$dirty && userForm['user[profile_attributes][linkedin]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}"><i class="fa fa-linkedin"></i></span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[profile_attributes][linkedin]"
|
|
||||||
ng-model="user.profile_attributes.linkedin"
|
|
||||||
class="form-control"
|
|
||||||
id="user_linkedin"
|
|
||||||
ng-pattern="/^https?:\/\/.*?linkedin/"
|
|
||||||
placeholder="https://www.linkedin.com/in/..."
|
|
||||||
ng-disabled="preventField['profile.linkedin'] && user.profile_attributes.first_name && !userForm['user[profile_attributes][linkedin]'].$dirty"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-show="social.instagram || user.profile_attributes.instragram" ng-class="{'has-error': userForm['user[profile_attributes][instagram]'].$dirty && userForm['user[profile_attributes][instagram]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}"><i class="fa fa-instagram"></i></span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[profile_attributes][instagram]"
|
|
||||||
ng-model="user.profile_attributes.instagram"
|
|
||||||
class="form-control"
|
|
||||||
id="user_instagram"
|
|
||||||
ng-pattern="/^https?:\/\/.*?instagram/"
|
|
||||||
placeholder="https://www.instagram.com/..."
|
|
||||||
ng-disabled="preventField['profile.instagram'] && user.profile_attributes.first_name && !userForm['user[profile_attributes][instagram]'].$dirty"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-show="social.youtube || user.profile_attributes.youtube" ng-class="{'has-error': userForm['user[profile_attributes][youtube]'].$dirty && userForm['user[profile_attributes][youtube]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}"><i class="fa fa-youtube"></i></span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[profile_attributes][youtube]"
|
|
||||||
ng-model="user.profile_attributes.youtube"
|
|
||||||
class="form-control"
|
|
||||||
id="user_youtube"
|
|
||||||
ng-pattern="/^https?:\/\/.*?youtube/"
|
|
||||||
placeholder="https://www.youtube.com/..."
|
|
||||||
ng-disabled="preventField['profile.youtube'] && user.profile_attributes.first_name && !userForm['user[profile_attributes][youtube]'].$dirty"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-show="social.vimeo || user.profile_attributes.vimeo" ng-class="{'has-error': userForm['user[profile_attributes][vimeo]'].$dirty && userForm['user[profile_attributes][vimeo]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}"><i class="fa fa-vimeo"></i></span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[profile_attributes][vimeo]"
|
|
||||||
ng-model="user.profile_attributes.vimeo"
|
|
||||||
class="form-control"
|
|
||||||
id="user_vimeo"
|
|
||||||
ng-pattern="/^https?:\/\/.*?vimeo/"
|
|
||||||
placeholder="https://vimeo.com/..."
|
|
||||||
ng-disabled="preventField['profile.vimeo'] && user.profile_attributes.first_name && !userForm['user[profile_attributes][vimeo]'].$dirty"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-show="social.dailymotion || user.profile_attributes.dailymotion" ng-class="{'has-error': userForm['user[profile_attributes][dailymotion]'].$dirty && userForm['user[profile_attributes][dailymotion]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}"><img src="../../images/social/dailymotion.png" alt="d" class="fa-img"/></span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[profile_attributes][dailymotion]"
|
|
||||||
ng-model="user.profile_attributes.dailymotion"
|
|
||||||
class="form-control"
|
|
||||||
id="user_dailymotion"
|
|
||||||
ng-pattern="/^https?:\/\/.*?dailymotion/"
|
|
||||||
placeholder="http://www.dailymotion.com/..."
|
|
||||||
ng-disabled="preventField['profile.dailymotion'] && user.profile_attributes.first_name && !userForm['user[profile_attributes][dailymotion]'].$dirty"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group" ng-show="social.github || user.profile_attributes.github" ng-class="{'has-error': userForm['user[profile_attributes][github]'].$dirty && userForm['user[profile_attributes][github]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}"><i class="fa fa-github"></i></span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[profile_attributes][github]"
|
|
||||||
ng-model="user.profile_attributes.github"
|
|
||||||
class="form-control"
|
|
||||||
id="user_github"
|
|
||||||
ng-pattern="/^https?:\/\/.*?github/"
|
|
||||||
placeholder="https://github.com/..."
|
|
||||||
ng-disabled="preventField['profile.github'] && user.profile_attributes.first_name && !userForm['user[profile_attributes][github]'].$dirty"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-show="social.echosciences || user.profile_attributes.echosciences" ng-class="{'has-error': userForm['user[profile_attributes][echosciences]'].$dirty && userForm['user[profile_attributes][echosciences]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}"><img src="../../images/social/echosciences.png" alt="d" class="fa-img"/></span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[profile_attributes][echosciences]"
|
|
||||||
ng-model="user.profile_attributes.echosciences"
|
|
||||||
class="form-control"
|
|
||||||
id="user_echosciences"
|
|
||||||
ng-pattern="/^https?:\/\/.*?echosciences/"
|
|
||||||
placeholder="http://www.echosciences-local.fr/membres/..."
|
|
||||||
ng-disabled="preventField['profile.echosciences'] && user.profile_attributes.first_name && !userForm['user[profile_attributes][echosciences]'].$dirty"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-show="social.pinterest || user.profile_attributes.pinterest" ng-class="{'has-error': userForm['user[profile_attributes][pinterest]'].$dirty && userForm['user[profile_attributes][pinterest]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}"><i class="fa fa-pinterest"></i></span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[profile_attributes][pinterest]"
|
|
||||||
ng-model="user.profile_attributes.pinterest"
|
|
||||||
class="form-control"
|
|
||||||
id="user_pinterest"
|
|
||||||
ng-pattern="/^https?:\/\/.*?pinterest/"
|
|
||||||
placeholder="https://fr.pinterest.com/..."
|
|
||||||
ng-disabled="preventField['profile.pinterest'] && user.profile_attributes.first_name && !userForm['user[profile_attributes][pinterest]'].$dirty"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-show="social.lastfm || user.profile_attributes.lastfm" ng-class="{'has-error': userForm['user[profile_attributes][lastfm]'].$dirty && userForm['user[profile_attributes][lastfm]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}"><i class="fa fa-lastfm"></i></span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[profile_attributes][lastfm]"
|
|
||||||
ng-model="user.profile_attributes.lastfm"
|
|
||||||
class="form-control"
|
|
||||||
id="user_lastfm"
|
|
||||||
ng-pattern="/^https?:\/\/.*?last.fm/"
|
|
||||||
placeholder="http://www.last.fm/fr/user/..."
|
|
||||||
ng-disabled="preventField['profile.lastfm'] && user.profile_attributes.first_name && !userForm['user[profile_attributes][lastfm]'].$dirty"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" ng-show="social.flickr || user.profile_attributes.flickr" ng-class="{'has-error': userForm['user[profile_attributes][flickr]'].$dirty && userForm['user[profile_attributes][flickr]'].$invalid}">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon help-cursor" title="{{ 'app.shared.user.used_for_profile' | translate }}"><i class="fa fa-flickr"></i></span>
|
|
||||||
<input type="text"
|
|
||||||
name="user[profile_attributes][flickr]"
|
|
||||||
ng-model="user.profile_attributes.flickr"
|
|
||||||
class="form-control"
|
|
||||||
id="user_flickr"
|
|
||||||
ng-pattern="/^https?:\/\/.*?flickr/"
|
|
||||||
placeholder="https://www.flickr.com/photos/..."
|
|
||||||
ng-disabled="preventField['profile.flickr'] && user.profile_attributes.flickr && !userForm['user[profile_attributes][flickr]'].$dirty"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="social-icons m-b">
|
|
||||||
<div ng-click="social.facebook = !social.facebook" ng-hide="social.facebook || user.profile_attributes.facebook"><i class="fa fa-facebook fa-2x"></i></div>
|
|
||||||
<div ng-click="social.twitter = !social.twitter" ng-hide="social.twitter || user.profile_attributes.twitter"><i class="fa fa-twitter fa-2x"></i></div>
|
|
||||||
<div ng-click="social.google_plus = !social.google_plus" ng-hide="social.google_plus || user.profile_attributes.google_plus"><i class="fa fa-google-plus fa-2x"></i></div>
|
|
||||||
<div ng-click="social.viadeo = !social.viadeo" ng-hide="social.viadeo || user.profile_attributes.viadeo"><i class="fa fa-viadeo fa-2x"></i></div>
|
|
||||||
<div ng-click="social.linkedin = !social.linkedin" ng-hide="social.linkedin || user.profile_attributes.linkedin"><i class="fa fa-linkedin fa-2x"></i></div>
|
|
||||||
<div ng-click="social.instagram = !social.instagram" ng-hide="social.instagram || user.profile_attributes.instagram"><i class="fa fa-instagram fa-2x"></i></div>
|
|
||||||
<div ng-click="social.youtube = !social.youtube" ng-hide="social.youtube || user.profile_attributes.youtube"><i class="fa fa-youtube fa-2x"></i></div>
|
|
||||||
<div ng-click="social.vimeo = !social.vimeo" ng-hide="social.vimeo || user.profile_attributes.vimeo"><i class="fa fa-vimeo fa-2x"></i></div>
|
|
||||||
<div ng-click="social.dailymotion = !social.dailymotion" ng-hide="social.dailymotion || user.profile_attributes.dailymotion"><img src="../../images/social/dailymotion.png" alt="d" class="fa-img contrast-250 fa-2x"/></div>
|
|
||||||
<div ng-click="social.github = !social.github" ng-hide="social.github || user.profile_attributes.github"><i class="fa fa-github fa-2x"></i></div>
|
|
||||||
<div ng-click="social.echosciences = !social.echosciences" ng-hide="social.echosciences || user.profile_attributes.echosciences"><img src="../../images/social/echosciences.png" alt="E" class="fa-img contrast-250 fa-2x"/></div>
|
|
||||||
<div ng-click="social.pinterest = !social.pinterest" ng-hide="social.pinterest || user.profile_attributes.pinterest"><i class="fa fa-pinterest fa-2x"></i></div>
|
|
||||||
<div ng-click="social.lastfm = !social.lastfm" ng-hide="social.lastfm || user.profile_attributes.lastfm"><i class="fa fa-lastfm fa-2x"></i></div>
|
|
||||||
<div ng-click="social.flickr = !social.flickr" ng-hide="social.flickr || user.profile_attributes.flickr"><i class="fa fa-flickr fa-2x"></i></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
@ -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 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 pointer" translate translate-default="Forgotten password">{{ 'app.public.common.password_forgotten' }}</a>
|
||||||
<span ng-if="confirmationRequired">
|
<span ng-if="confirmationRequired">
|
||||||
<br><a 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 pointer" 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" ng-show="publicRegistrations">
|
<p class="text-center font-sbold" ng-show="publicRegistrations">
|
||||||
<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 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 pointer" translate translate-default="Create an account">{{ 'app.public.common.create_an_account' }}</a></br>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ module SingleSignOnConcern
|
|||||||
logger.debug "mapping sso field #{field} with value=#{value}"
|
logger.debug "mapping sso field #{field} with value=#{value}"
|
||||||
# we do not merge the email field if its end with the special value '-duplicate' as this means
|
# we do not merge the email field if its end with the special value '-duplicate' as this means
|
||||||
# that the user is currently merging with the account that have the same email than the sso
|
# that the user is currently merging with the account that have the same email than the sso
|
||||||
set_data_from_sso_mapping(field, value) unless (field == 'user.email' && value.end_with?('-duplicate')) || (field == 'user.group_id' && user.admin?)
|
set_data_from_sso_mapping(field, value) unless (field == 'user.email' && value.end_with?('-duplicate')) || (field == 'user.group_id' && sso_user.admin?)
|
||||||
end
|
end
|
||||||
|
|
||||||
# run the account transfer in an SQL transaction to ensure data integrity
|
# run the account transfer in an SQL transaction to ensure data integrity
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
json.extract! @training, :id, :name, :description, :machine_ids, :nb_total_places, :public_page
|
json.extract! @training, :id, :name, :description, :machine_ids, :nb_total_places, :public_page
|
||||||
json.availabilities @availabilities do |a|
|
json.availabilities @availabilities do |a|
|
||||||
json.id a.id
|
json.id a.id
|
||||||
json.start_at a.start_at.iso8601
|
json.start_at a.start_at.iso8601
|
||||||
json.end_at a.end_at.iso8601
|
json.end_at a.end_at.iso8601
|
||||||
json.reservation_users a.slots.map do |slot|
|
json.reservation_users a.slots.map(&:slots_reservations).flatten.map do |sr|
|
||||||
json.id slot.reservations.first.statistic_profile.user_id
|
json.id sr.reservation.statistic_profile.user_id
|
||||||
json.full_name slot.reservations.first.statistic_profile&.user&.profile&.full_name
|
json.full_name sr.reservation.statistic_profile.user&.profile&.full_name
|
||||||
json.is_valid slot.reservations.first.statistic_profile.trainings.include?(@training)
|
json.is_valid sr.reservation.statistic_profile.trainings&.include?(@training)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -71,9 +71,9 @@ pt:
|
|||||||
email_is_required: "E-mail é obrigatório."
|
email_is_required: "E-mail é obrigatório."
|
||||||
your_password: "Sua senha"
|
your_password: "Sua senha"
|
||||||
password_is_required: "Senha é obrigatório."
|
password_is_required: "Senha é obrigatório."
|
||||||
password_is_too_short: "Password is too short (minimum 12 characters)"
|
password_is_too_short: "Senha muito curta (mínimo 12 caracteres)"
|
||||||
password_is_too_weak: "Password is too weak:"
|
password_is_too_weak: "A senha é muito fraca:"
|
||||||
password_is_too_weak_explanations: "minimum 12 characters, at least one uppercase letter, one lowercase letter, one number and one special character"
|
password_is_too_weak_explanations: "mínimo de 12 caracteres, pelo menos uma letra maiúscula, uma letra minúscula, um número e um caractere especial"
|
||||||
type_your_password_again: "Digite sua senha novamente"
|
type_your_password_again: "Digite sua senha novamente"
|
||||||
password_confirmation_is_required: "Confirmação de senha é obrigatório."
|
password_confirmation_is_required: "Confirmação de senha é obrigatório."
|
||||||
password_does_not_match_with_confirmation: "A senha não é igual ao da confirmação."
|
password_does_not_match_with_confirmation: "A senha não é igual ao da confirmação."
|
||||||
@ -103,7 +103,7 @@ pt:
|
|||||||
used_for_reservation: "Estes dados serão utilizados em caso de alteração em uma das suas reservas"
|
used_for_reservation: "Estes dados serão utilizados em caso de alteração em uma das suas reservas"
|
||||||
used_for_profile: "Estes dados serão exibidos apenas no seu perfil"
|
used_for_profile: "Estes dados serão exibidos apenas no seu perfil"
|
||||||
public_profile: "Você terá um perfil público e outros usuários poderão associá-lo em seus projetos"
|
public_profile: "Você terá um perfil público e outros usuários poderão associá-lo em seus projetos"
|
||||||
you_will_receive_confirmation_instructions_by_email_detailed: "If your e-mail address is valid, you will receive an email with instructions about how to confirm your account in a few minutes."
|
you_will_receive_confirmation_instructions_by_email_detailed: "Se seu endereço de e-mail for válido, você receberá em breve um e-mail com instruções sobre como confirmar sua conta."
|
||||||
#password modification modal
|
#password modification modal
|
||||||
change_your_password: "Mudar sua senha"
|
change_your_password: "Mudar sua senha"
|
||||||
your_new_password: "Sua nova senha"
|
your_new_password: "Sua nova senha"
|
||||||
@ -119,7 +119,7 @@ pt:
|
|||||||
#confirmation modal
|
#confirmation modal
|
||||||
you_will_receive_confirmation_instructions_by_email: "Você receberá instruções de confirmação por e-mail."
|
you_will_receive_confirmation_instructions_by_email: "Você receberá instruções de confirmação por e-mail."
|
||||||
#forgotten password modal
|
#forgotten password modal
|
||||||
you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "If your e-mail address is valid, you will receive in a moment an e-mail with instructions to reset your password."
|
you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "Se o seu endereço de e-mail for válido, você receberá em breve um e-mail com instruções para redefinir sua senha."
|
||||||
#Fab-manager's version
|
#Fab-manager's version
|
||||||
version: "Versão:"
|
version: "Versão:"
|
||||||
upgrade_fabmanager: "Atualizar Fab-manager"
|
upgrade_fabmanager: "Atualizar Fab-manager"
|
||||||
|
@ -19,9 +19,9 @@ pt:
|
|||||||
extension_whitelist_error: "Você não tem permissão para fazer o upload de arquivos com esta extensão %{extension}, tipos permitidos: %{allowed_types}"
|
extension_whitelist_error: "Você não tem permissão para fazer o upload de arquivos com esta extensão %{extension}, tipos permitidos: %{allowed_types}"
|
||||||
extension_blacklist_error: "Você não tem permissão para carregar arquivos %{extension}, tipos proibidos: %{prohibited_types}"
|
extension_blacklist_error: "Você não tem permissão para carregar arquivos %{extension}, tipos proibidos: %{prohibited_types}"
|
||||||
content_type_whitelist_error: "Você não tem permissão para enviar arquivos %{content_type}, tipos permitidos: %{allowed_types}"
|
content_type_whitelist_error: "Você não tem permissão para enviar arquivos %{content_type}, tipos permitidos: %{allowed_types}"
|
||||||
rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image?"
|
rmagick_processing_error: "Falha ao manipular com rmagick, talvez não seja uma imagem?"
|
||||||
mime_types_processing_error: "Failed to process file with MIME::Types, maybe not valid content-type?"
|
mime_types_processing_error: "Falha ao processar arquivo com MIME::Types, talvez tipo de conteúdo inválido?"
|
||||||
mini_magick_processing_error: "Failed to manipulate the file, maybe it is not an image?"
|
mini_magick_processing_error: "Falha ao manipular o arquivo, talvez não seja uma imagem?"
|
||||||
wrong_size: "é o tamanho errado (deveria ser %{file_size})"
|
wrong_size: "é o tamanho errado (deveria ser %{file_size})"
|
||||||
size_too_small: "é muito pequeno (deve ser pelo menos %{file_size})"
|
size_too_small: "é muito pequeno (deve ser pelo menos %{file_size})"
|
||||||
size_too_big: "é muito grande (deve ser no máximo %{file_size})"
|
size_too_big: "é muito grande (deve ser no máximo %{file_size})"
|
||||||
|
@ -192,7 +192,7 @@ _Eg.: configuring **es-ES** will set the currency symbol to **€** but **es-MX*
|
|||||||
|
|
||||||
Available values: `en, en-AU-CA, en-GB, en-IE, en-IN, en-NZ, en-US, en-ZA, fr, fa-CA, fr-CH, fr-CM, fr-FR, es, es-419, es-AR, es-CL, es-CO, es-CR, es-DO,
|
Available values: `en, en-AU-CA, en-GB, en-IE, en-IN, en-NZ, en-US, en-ZA, fr, fa-CA, fr-CH, fr-CM, fr-FR, es, es-419, es-AR, es-CL, es-CO, es-CR, es-DO,
|
||||||
es-EC, es-ES, es-MX, es-MX, es-PA, es-PE, es-US, es-VE, no, pt, pt-BR, zu`.
|
es-EC, es-ES, es-MX, es-MX, es-PA, es-PE, es-US, es-VE, no, pt, pt-BR, zu`.
|
||||||
Default is **en**.
|
When not defined, it defaults to **en**.
|
||||||
|
|
||||||
If your locale is not present in that list or any locale doesn't have your exact expectations, please open a pull request to share your modifications with the community and obtain a rebuilt docker image.
|
If your locale is not present in that list or any locale doesn't have your exact expectations, please open a pull request to share your modifications with the community and obtain a rebuilt docker image.
|
||||||
You can find templates of these files at https://github.com/svenfuchs/rails-i18n/tree/rails-5-x/rails/locale.
|
You can find templates of these files at https://github.com/svenfuchs/rails-i18n/tree/rails-5-x/rails/locale.
|
||||||
@ -203,7 +203,7 @@ You can find templates of these files at https://github.com/svenfuchs/rails-i18n
|
|||||||
Configure the moment.js library for l10n.
|
Configure the moment.js library for l10n.
|
||||||
|
|
||||||
See [github.com/moment/momentlocale/*.js](https://github.com/moment/moment/tree/2.22.2/locale) for a list of available locales.
|
See [github.com/moment/momentlocale/*.js](https://github.com/moment/moment/tree/2.22.2/locale) for a list of available locales.
|
||||||
Default is **en** (even if it's not listed).
|
When not defined, it defaults to **en** (even if it's not listed).
|
||||||
<a name="SUMMERNOTE_LOCALE"></a>
|
<a name="SUMMERNOTE_LOCALE"></a>
|
||||||
|
|
||||||
SUMMERNOTE_LOCALE
|
SUMMERNOTE_LOCALE
|
||||||
@ -211,7 +211,7 @@ Default is **en** (even if it's not listed).
|
|||||||
Configure the javascript summernote editor for l10n.
|
Configure the javascript summernote editor for l10n.
|
||||||
|
|
||||||
See [github.com/summernote/summernote/lang/summernote-*.js](https://github.com/summernote/summernote/tree/v0.8.18/lang) for a list of available locales.
|
See [github.com/summernote/summernote/lang/summernote-*.js](https://github.com/summernote/summernote/tree/v0.8.18/lang) for a list of available locales.
|
||||||
Default is **en-US** (even if it's not listed).
|
When not defined, it defaults to **en-US** (even if it's not listed).
|
||||||
<a name="ANGULAR_LOCALE"></a>
|
<a name="ANGULAR_LOCALE"></a>
|
||||||
|
|
||||||
ANGULAR_LOCALE
|
ANGULAR_LOCALE
|
||||||
@ -222,14 +222,14 @@ Please, be aware that **the configured locale will imply the CURRENCY displayed
|
|||||||
|
|
||||||
_Eg.: configuring **fr-fr** will set the currency symbol to **€** but **fr-ca** will set **$** as currency symbol, so setting the `ANGULAR_LOCALE` to simple **fr** (without country indication) will probably not do what you expect._
|
_Eg.: configuring **fr-fr** will set the currency symbol to **€** but **fr-ca** will set **$** as currency symbol, so setting the `ANGULAR_LOCALE` to simple **fr** (without country indication) will probably not do what you expect._
|
||||||
|
|
||||||
See [code.angularjs.org/i18n/angular-locale_*.js](https://code.angularjs.org/1.8.2/i18n/) for a list of available locales. Default is **en**.
|
See [code.angularjs.org/i18n/angular-locale_*.js](https://code.angularjs.org/1.8.2/i18n/) for a list of available locales. When not defined, it defaults to **en**.
|
||||||
<a name="FULLCALENDAR_LOCALE"></a>
|
<a name="FULLCALENDAR_LOCALE"></a>
|
||||||
|
|
||||||
FULLCALENDAR_LOCALE
|
FULLCALENDAR_LOCALE
|
||||||
|
|
||||||
Configure the fullCalendar JS agenda library.
|
Configure the fullCalendar JS agenda library.
|
||||||
|
|
||||||
See [github.com/fullcalendar/fullcalendar/locale/*.js](https://github.com/fullcalendar/fullcalendar/tree/v3.10.2/locale) for a list of available locales. Default is **en**.
|
See [github.com/fullcalendar/fullcalendar/locale/*.js](https://github.com/fullcalendar/fullcalendar/tree/v3.10.2/locale) for a list of available locales. When not defined, it defaults to **en**.
|
||||||
<a name="INTL_LOCALE"></a>
|
<a name="INTL_LOCALE"></a>
|
||||||
|
|
||||||
INTL_LOCALE
|
INTL_LOCALE
|
||||||
@ -256,7 +256,7 @@ Available values: `danish, dutch, english, finnish, french, german, hungarian, i
|
|||||||
TIME_ZONE
|
TIME_ZONE
|
||||||
|
|
||||||
In Rails: set Time.zone default to the specified zone and make Active Record auto-convert to this zone. Run `rails time:zones:all` for a list of available time zone names.
|
In Rails: set Time.zone default to the specified zone and make Active Record auto-convert to this zone. Run `rails time:zones:all` for a list of available time zone names.
|
||||||
Default is **UTC**.
|
When not defined, it defaults to **UTC**.
|
||||||
<a name="WEEK_STARTING_DAY"></a>
|
<a name="WEEK_STARTING_DAY"></a>
|
||||||
|
|
||||||
WEEK_STARTING_DAY
|
WEEK_STARTING_DAY
|
||||||
|
BIN
doc/fr/guide_utilisation_fab_manager_v5.0.pdf → doc/fr/guide_utilisation_fab_manager_v5.4.pdf
Executable file → Normal file
BIN
doc/fr/guide_utilisation_fab_manager_v5.0.pdf → doc/fr/guide_utilisation_fab_manager_v5.4.pdf
Executable file → Normal file
Binary file not shown.
@ -35,7 +35,9 @@ Choose one, depending on your budget, on the server's location, on the uptime gu
|
|||||||
#### System requirements
|
#### System requirements
|
||||||
##### Memory
|
##### Memory
|
||||||
|
|
||||||
If you do not plan to use the statistics module, you will need at least 2 GB of addressable memory (RAM + swap) to install and use Fab-manager.
|
To install or upgrade Fab-manager you need at least 4 GB of RAM + 2 GB of swap to be able to compile the assets.
|
||||||
|
|
||||||
|
Once installed, if you do not plan to use the statistics module, you will need at least 2 GB of addressable memory (RAM + swap) to use Fab-manager.
|
||||||
We recommend 4 GB of RAM to take full advantage of Fab-manager and be able to use the statistics module.
|
We recommend 4 GB of RAM to take full advantage of Fab-manager and be able to use the statistics module.
|
||||||
If you have a large community (~ 200 active membres), we recommend 4 GB of RAM, even without the statistics module.
|
If you have a large community (~ 200 active membres), we recommend 4 GB of RAM, even without the statistics module.
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "fab-manager",
|
"name": "fab-manager",
|
||||||
"version": "5.4.15",
|
"version": "5.4.16",
|
||||||
"description": "Fab-manager is the FabLab management solution. It provides a comprehensive, web-based, open-source tool to simplify your administrative tasks and your marker's projects.",
|
"description": "Fab-manager is the FabLab management solution. It provides a comprehensive, web-based, open-source tool to simplify your administrative tasks and your marker's projects.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"fablab",
|
"fablab",
|
||||||
|
@ -198,7 +198,7 @@ prepare_files()
|
|||||||
if [[ "$confirm" = "n" ]]; then exit 1; fi
|
if [[ "$confirm" = "n" ]]; then exit 1; fi
|
||||||
|
|
||||||
elevate_cmd mkdir -p "$FABMANAGER_PATH"
|
elevate_cmd mkdir -p "$FABMANAGER_PATH"
|
||||||
elevate_cmd chown -R "$(whoami):$(whoami)" "$FABMANAGER_PATH"
|
elevate_cmd chown -R "$(whoami)" "$FABMANAGER_PATH"
|
||||||
|
|
||||||
# create folders before starting the containers, otherwise root will own them
|
# create folders before starting the containers, otherwise root will own them
|
||||||
local folders=(accounting config elasticsearch/config exports imports invoices log payment_schedules plugins postgresql \
|
local folders=(accounting config elasticsearch/config exports imports invoices log payment_schedules plugins postgresql \
|
||||||
@ -384,7 +384,7 @@ configure_env_file()
|
|||||||
var_doc=$(get_md_anchor "$doc" "$variable")
|
var_doc=$(get_md_anchor "$doc" "$variable")
|
||||||
current=$(grep "$variable=" "$FABMANAGER_PATH/config/env")
|
current=$(grep "$variable=" "$FABMANAGER_PATH/config/env")
|
||||||
echo "$var_doc" | bat --file-name "$variable" --language md --color=always
|
echo "$var_doc" | bat --file-name "$variable" --language md --color=always
|
||||||
printf "- \e[1mCurrent value: %s\e[21m\n- New value? (leave empty to keep the current value)\n" "$current"
|
printf -- "- \e[1mCurrent value: %s\e[21m\n- New value? (leave empty to keep the current value)\n" "$current"
|
||||||
read -rep " > " value </dev/tty
|
read -rep " > " value </dev/tty
|
||||||
if [ "$value" != "" ]; then
|
if [ "$value" != "" ]; then
|
||||||
esc_val=$(printf '%s\n' "$value" | sed -e 's/\//\\\//g')
|
esc_val=$(printf '%s\n' "$value" | sed -e 's/\//\\\//g')
|
||||||
@ -412,8 +412,12 @@ read_password()
|
|||||||
local password confirmation
|
local password confirmation
|
||||||
>&2 echo "Please input a password for this administrator's account"
|
>&2 echo "Please input a password for this administrator's account"
|
||||||
read -rsp " > " password </dev/tty
|
read -rsp " > " password </dev/tty
|
||||||
if [ ${#password} -lt 8 ]; then
|
if [ ${#password} -lt 12 ]; then
|
||||||
>&2 printf "\nError: password is too short (minimal length: 8 characters)\n"
|
>&2 printf "\nError: password is too short (minimal length: 12 characters)\n"
|
||||||
|
password=$(read_password 'no-confirm')
|
||||||
|
fi
|
||||||
|
if [[ ! $password =~ [0-9] || ! $password =~ [a-z] || ! $password =~ [A-Z] || ! $password =~ [[:punct:]] ]]; then
|
||||||
|
>&2 printf "\nError: password is too weak (should contain uppercases, lowercases, digits and special characters)\n"
|
||||||
password=$(read_password 'no-confirm')
|
password=$(read_password 'no-confirm')
|
||||||
fi
|
fi
|
||||||
if [ "$1" != 'no-confirm' ]; then
|
if [ "$1" != 'no-confirm' ]; then
|
||||||
|
26
test/integration/trainings/availabilities_test.rb
Normal file
26
test/integration/trainings/availabilities_test.rb
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
module Trainings; end
|
||||||
|
|
||||||
|
class Trainings::AvailabilitiesTest < ActionDispatch::IntegrationTest
|
||||||
|
def setup
|
||||||
|
@admin = User.find_by(username: 'admin')
|
||||||
|
login_as(@admin, scope: :user)
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'get trainings availabilities list' do
|
||||||
|
training = Training.find(1)
|
||||||
|
get "/api/trainings/#{training.id}/availabilities"
|
||||||
|
|
||||||
|
# Check response format & status
|
||||||
|
assert_equal 200, response.status, response.body
|
||||||
|
assert_equal Mime[:json], response.content_type
|
||||||
|
|
||||||
|
# Check the correct training was returned
|
||||||
|
result = json_response(response.body)
|
||||||
|
assert_equal training.id, result[:id], 'training id does not match'
|
||||||
|
assert_not_empty result[:availabilities], 'no training availabilities were returned'
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user