1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-12-02 13:24:20 +01:00

fix confilt

This commit is contained in:
Du Peng 2022-09-06 20:06:01 +02:00
commit 93be370912
32 changed files with 397 additions and 247 deletions

8
.overcommit.yml Normal file
View File

@ -0,0 +1,8 @@
PreCommit:
RuboCop:
enabled: true
on_warn: fail # Treat all warnings as failures
TrailingWhitespace:
enabled: true

View File

@ -2,6 +2,13 @@
## next release ## next release
- Updated portuguese translations
- Added automatic RuboCop validation on pre-commit
- Use union type instead of enum for SettingName
- 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
## v5.4.15 2022 August 1 ## v5.4.15 2022 August 1
- Improved security: adds redis-session-store to store session - Improved security: adds redis-session-store to store session

View File

@ -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'
@ -66,7 +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'

View File

@ -95,6 +95,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)
@ -178,6 +179,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)
@ -274,6 +276,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)
@ -535,6 +541,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

View File

@ -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

View File

@ -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;
} }

View File

@ -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>
); );
}; };

View File

@ -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' };

View File

@ -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}>

View File

@ -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);
}, []); }, []);

View File

@ -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));
}, []); }, []);

View File

@ -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));
}, []); }, []);

View File

@ -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()));

View File

@ -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));
}, []); }, []);

View File

@ -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'));
} }

View File

@ -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

View File

@ -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>

View File

@ -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}

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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));
}, []); }, []);

View File

@ -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">

View File

@ -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(',')}

View File

@ -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();

View File

@ -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>

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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})"

View 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