diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f49c963e..d263c9af9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ ## next deploy +## v5.4.2 2022 June 1 + +- Updated react-select to 5.3.2 +- Moved the calendar navigation buttons to the left side +- Fix a bug: unable to remove the last training or the last tag to a member +- Fix a bug: unable to run scripts on systems with legacy version of docker-compose +- Fix a bug: unable to sign up if admin actived organization's additional fields with required +- Fix a bug: undefined error in new member page +- Fix a bug: OIDC scopes are separated by spaces, not commas +- Fix a bug: unable to create OIDC custom scopes +- Fix a bug: enable admins to be invited to collaborate on projects +- Fix a bug: hide some links to create an account if public registrations is disabled +- Fix a bug: unable to save user validation if admin click save user profile button after switch user valitation +- Fix a bug: if multi VAT no value is filled in, the general rate can't apply +- Fix a security issue: updated rack to 2.2.3.1 to fix [CVE-2022-30123](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-30123) and [CVE-2022-30122](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-30122) + ## v5.4.1 2022 May 23 - Disable to clicking outside or pressing escape to close sign up modal @@ -23,6 +39,7 @@ - Option to disable the 'machines' module - Option to prevent users from changing their group - Ability to define social networks for the FabLab "about page" +- Plan categories can have rich-text descriptions - Improved security when changing passwords - Support for OpenID Connect in Single-Sign-On authentication providers - ICS file attached to the reservation notification email diff --git a/Gemfile.lock b/Gemfile.lock index aef86fe98..6691a986b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -300,7 +300,7 @@ GEM activesupport (>= 3.0.0) raabro (1.4.0) racc (1.6.0) - rack (2.2.3) + rack (2.2.3.1) rack-oauth2 (1.19.0) activesupport attr_required diff --git a/app/controllers/api/members_controller.rb b/app/controllers/api/members_controller.rb index 4edfc3e44..5240b41b5 100644 --- a/app/controllers/api/members_controller.rb +++ b/app/controllers/api/members_controller.rb @@ -245,7 +245,8 @@ class API::MembersController < API::ApiController members_service = Members::MembersService.new(@member) - if members_service.validate(user_params[:validated_at].present?) + uparams = params.require(:user).permit(:validated_at) + if members_service.validate(uparams[:validated_at].present?) render :show, status: :ok, location: member_path(@member) else render json: @member.errors, status: :unprocessable_entity @@ -275,7 +276,7 @@ class API::MembersController < API::ApiController elsif current_user.admin? || current_user.manager? params.require(:user).permit(:username, :email, :password, :password_confirmation, :is_allow_contact, :is_allow_newsletter, :group_id, - :validated_at, tag_ids: [], + tag_ids: [], profile_attributes: [:id, :first_name, :last_name, :phone, :interest, :software_mastered, :website, :job, :facebook, :twitter, :google_plus, :viadeo, :linkedin, :instagram, :youtube, :vimeo, :dailymotion, :github, :echosciences, :pinterest, :lastfm, :flickr, diff --git a/app/frontend/src/javascript/api/member.ts b/app/frontend/src/javascript/api/member.ts index b2727572b..0c3697c18 100644 --- a/app/frontend/src/javascript/api/member.ts +++ b/app/frontend/src/javascript/api/member.ts @@ -23,7 +23,7 @@ export default class MemberAPI { } static async update (user: User): Promise { - const data = serialize({ user }); + const data = serialize({ user }, { allowEmptyArrays: true }); if (user.profile_attributes?.user_avatar_attributes?.attachment_files[0]) { data.set('user[profile_attributes][user_avatar_attributes][attachment]', user.profile_attributes.user_avatar_attributes.attachment_files[0]); } diff --git a/app/frontend/src/javascript/components/authentication-provider/oauth2-form.tsx b/app/frontend/src/javascript/components/authentication-provider/oauth2-form.tsx index 88ae8ce15..1d6f0f5df 100644 --- a/app/frontend/src/javascript/components/authentication-provider/oauth2-form.tsx +++ b/app/frontend/src/javascript/components/authentication-provider/oauth2-form.tsx @@ -18,7 +18,7 @@ export const Oauth2Form = ({ register, strateg // regular expression to validate the input fields const endpointRegex = /^\/?([-._~:?#[\]@!$&'()*+,;=%\w]+\/?)*$/; - const urlRegex = /^(https?:\/\/)([\da-z.-]+)\.([-a-z0-9.]{2,30})([/\w .-]*)*\/?$/; + const urlRegex = /^(https?:\/\/)([^.]+)\.(.{2,30})(\/.*)*\/?$/; /** * Build the callback URL, based on the strategy name. diff --git a/app/frontend/src/javascript/components/authentication-provider/openid-connect-form.tsx b/app/frontend/src/javascript/components/authentication-provider/openid-connect-form.tsx index f51e65d56..4969c6842 100644 --- a/app/frontend/src/javascript/components/authentication-provider/openid-connect-form.tsx +++ b/app/frontend/src/javascript/components/authentication-provider/openid-connect-form.tsx @@ -10,6 +10,7 @@ import { OpenIdConnectProvider } from '../../models/authentication-provider'; import SsoClient from '../../api/external/sso'; import { FieldPathValue } from 'react-hook-form/dist/types/path'; import { FormMultiSelect } from '../form/form-multi-select'; +import { difference } from 'lodash'; interface OpenidConnectFormProps { register: UseFormRegister, @@ -41,7 +42,7 @@ export const OpenidConnectForm = ) => void): void => { + const current = currentFormValues.scope || []; + if (scopesAvailable) { + // add custom scopes to the list of available scopes + const unlisted = difference(current, scopesAvailable); + callback(scopesAvailable.concat(unlisted).map(scope => ({ value: scope, label: scope }))); + } else { + current.map(scope => ({ value: scope, label: scope })); + } + }; + /** * Callback that check for the existence of the .well-known/openid-configuration endpoint, for the given issuer. * This callback is triggered when the user changes the issuer field. @@ -102,18 +118,12 @@ export const OpenidConnectForm = - {!scopesAvailable && } />} - {scopesAvailable && } - options={scopesAvailable.map((scope) => ({ value: scope, label: scope }))} - creatable - control={control} />} + } + loadOptions={loadScopes} + creatable + control={control} /> } @@ -139,7 +149,7 @@ export const OpenidConnectForm = + rules={{ required: false, pattern: urlRegex }} />

{t('app.admin.authentication.openid_connect_form.client_options')}

extends FormControlledComponent, AbstractFormItemProps { - options: Array>, +interface CommonProps extends FormControlledComponent, AbstractFormItemProps { valuesDefault?: Array, onChange?: (values: Array) => void, placeholder?: string, - expectedResult?: 'array' | 'string' creatable?: boolean, } +// we should provide either an array of options or a function that returns a promise, but not both +type OptionsProps = + { options: Array>, loadOptions?: never } | + { options?: never, loadOptions: (inputValue: string, callback: (options: Array>) => void) => void }; + +type FormSelectProps = CommonProps & OptionsProps; + /** * Option format, expected by react-select * @see https://github.com/JedWatson/react-select */ -type selectOption = { value: TOptionValue, label: string }; +type selectOption = { value: TOptionValue, label: string, select?: boolean }; /** * This component is a wrapper around react-select to use with react-hook-form. * It is a multi-select component. */ -export const FormMultiSelect = ({ id, label, tooltip, className, control, placeholder, options, valuesDefault, error, rules, disabled, onChange, formState, warning, expectedResult, creatable }: FormSelectProps) => { +export const FormMultiSelect = ({ id, label, tooltip, className, control, placeholder, options, valuesDefault, error, rules, disabled, onChange, formState, warning, loadOptions, creatable }: FormSelectProps) => { + const { t } = useTranslation('shared'); + const [isDisabled, setIsDisabled] = React.useState(false); - const [allOptions, setAllOptions] = React.useState>>(options); + const [allOptions, setAllOptions] = React.useState>>(options || []); useEffect(() => { if (typeof disabled === 'function') { @@ -40,87 +50,102 @@ export const FormMultiSelect = { - setAllOptions(options); - }, [options]); + if (typeof loadOptions === 'function') { + loadOptions('', options => { + setAllOptions(options); + }); + } + }, [loadOptions]); /** - * The following callback will trigger the onChange callback, if it was passed to this component, - * when the selected option changes. - * It will also update the react-hook-form's value, according to the provided 'result' property (string or array). + * The following callback will set the new selected options in the component state. */ - const onChangeCb = (newValues: Array, rhfOnChange): void => { + const onChangeCb = (newValues: Array, rhfOnChange: (values: Array) => void): void => { if (typeof onChange === 'function') { onChange(newValues); } - if (expectedResult === 'string') { - rhfOnChange(newValues.join(',')); - } else { + if (typeof rhfOnChange === 'function') { rhfOnChange(newValues); } }; /** - * This function will return the currently selected options, according to the provided react-hook-form's value. + * This function will return the currently selected options, according to the selectedOptions state. */ - const getCurrentValues = (value: Array|string): Array> => { - let values: Array = []; - if (typeof value === 'string') { - values = value.split(',') as Array as Array; - } else { - values = value; - } - return allOptions.filter(c => values?.includes(c.value)); + const getCurrentValues = (value: Array): Array> => { + return allOptions.filter(c => value?.includes(c.value)); }; /** * When the select is 'creatable', this callback handle the creation and the selection of a new option. */ - const handleCreate = (value: Array|string, rhfOnChange) => { - return (inputValue: string) => { - // add the new value to the list of options - const newOption = { value: inputValue as unknown as TOptionValue, label: inputValue }; - setAllOptions([...allOptions, newOption]); - - // select the new option - const values = getCurrentValues(value); - values.push(newOption); - onChangeCb(values.map(c => c.value), rhfOnChange); - }; + const handleCreate = (inputValue: string, currentSelection: Array, rhfOnChange: (values: Array) => void) => { + // add the new value to the list of options + const newValue = inputValue as unknown as TOptionValue; + const newOption = { value: newValue, label: inputValue }; + setAllOptions([...allOptions, newOption]); + if (typeof rhfOnChange === 'function') { + rhfOnChange([...currentSelection, newValue]); + } }; - // if the user can create new options, we need to use a different component - const AbstractSelect = creatable ? CreatableSelect : Select; + /** + * Translate the label for a new item when the select is "creatable" + */ + const formatCreateLabel = (inputValue: string): string => { + return t('app.shared.form_multi_select.create_label', { VALUE: inputValue }); + }; + + // if the user can create new options, and/or load the options through a promise need to use different components + const AbstractSelect = loadOptions + ? creatable + ? AsyncCreatableSelect + : AsyncSelect + : creatable + ? CreatableSelect + : Select; return ( - } - control={control} - defaultValue={valuesDefault as UnpackNestedValue>>} - rules={rules} - render={({ field: { onChange, value, ref } }) => - { - const values = val?.map(c => c.value); - onChangeCb(values, onChange); - }} - onCreateOption={handleCreate(value, onChange)} - placeholder={placeholder} - options={allOptions} - isDisabled={isDisabled} - isMulti /> - } /> + } + control={control} + defaultValue={valuesDefault as UnpackNestedValue>>} + rules={rules} + render={({ field: { onChange: rhfOnChange, value, ref } }) => { + const selectProps = { + classNamePrefix: 'rs', + className: 'rs', + ref, + value: getCurrentValues(value), + placeholder, + isDisabled, + isMulti: true, + onChange: val => onChangeCb(val?.map(c => c.value), rhfOnChange), + options: allOptions + }; + + if (loadOptions) { + Object.assign(selectProps, { loadOptions, defaultOptions: true, cacheOptions: true }); + } + + if (creatable) { + Object.assign(selectProps, { + formatCreateLabel, + onCreateOption: inputValue => handleCreate(inputValue, value, rhfOnChange) + }); + } + + return (); + }} + /> ); }; FormMultiSelect.defaultProps = { - expectedResult: 'array', creatable: false, disabled: false }; diff --git a/app/frontend/src/javascript/components/socials/edit-socials.tsx b/app/frontend/src/javascript/components/socials/edit-socials.tsx index 7e4e98e22..39132636c 100644 --- a/app/frontend/src/javascript/components/socials/edit-socials.tsx +++ b/app/frontend/src/javascript/components/socials/edit-socials.tsx @@ -19,7 +19,7 @@ interface EditSocialsProps { export const EditSocials = ({ register, setValue, networks, formState, disabled }: EditSocialsProps) => { const { t } = useTranslation('shared'); // regular expression to validate the the input fields - const urlRegex = /^(https?:\/\/)([\da-z.-]+)\.([-a-z\d.]{2,30})([/\w .-]*)*\/?$/; + const urlRegex = /^(https?:\/\/)([^.]+)\.(.{2,30})(\/.*)*\/?$/; const initSelectedNetworks = networks.filter(el => !['', null, undefined].includes(el.url)); const [selectedNetworks, setSelectedNetworks] = useState(initSelectedNetworks); diff --git a/app/frontend/src/javascript/components/user/user-profile-form.tsx b/app/frontend/src/javascript/components/user/user-profile-form.tsx index 12d37384a..1114f6876 100644 --- a/app/frontend/src/javascript/components/user/user-profile-form.tsx +++ b/app/frontend/src/javascript/components/user/user-profile-form.tsx @@ -58,7 +58,7 @@ export const UserProfileForm: React.FC = ({ action, size, // regular expression to validate the input fields const phoneRegex = /^((00|\+)\d{2,3})?\d{4,14}$/; - const urlRegex = /^(https?:\/\/)([\da-z.-]+)\.([-a-z\d.]{2,30})([/\w .-]*)*\/?$/; + const urlRegex = /^(https?:\/\/)([^.]+)\.(.{2,30})(\/.*)*\/?$/; const { handleSubmit, register, control, formState, setValue, reset } = useForm({ defaultValues: { ...user } }); const output = useWatch({ control }); @@ -67,8 +67,6 @@ export const UserProfileForm: React.FC = ({ action, size, const [isLocalDatabaseProvider, setIsLocalDatabaseProvider] = useState(false); const [groups, setGroups] = useState([]); const [termsAndConditions, setTermsAndConditions] = useState(null); - const [trainings, setTrainings] = useState([]); - const [tags, setTags] = useState([]); const [profileCustomFields, setProfileCustomFields] = useState([]); useEffect(() => { @@ -83,21 +81,11 @@ export const UserProfileForm: React.FC = ({ action, size, if (showTermsAndConditionsInput) { CustomAssetAPI.get(CustomAssetName.CguFile).then(setTermsAndConditions).catch(error => onError(error)); } - if (showTrainingsInput) { - TrainingAPI.index({ disabled: false }).then(data => { - setTrainings(buildOptions(data)); - }).catch(error => onError(error)); - } - if (showTagsInput) { - TagAPI.index().then(data => { - setTags(buildOptions(data)); - }).catch(error => onError(error)); - } ProfileCustomFieldAPI.index().then(data => { const fData = data.filter(f => f.actived); setProfileCustomFields(fData); const userProfileCustomFields = fData.map(f => { - const upcf = user.invoicing_profile_attributes.user_profile_custom_fields_attributes.find(uf => uf.profile_custom_field_id === f.id); + const upcf = user?.invoicing_profile_attributes?.user_profile_custom_fields_attributes?.find(uf => uf.profile_custom_field_id === f.id); return upcf || { value: '', invoicing_profile_id: user.invoicing_profile_attributes.id, @@ -117,6 +105,24 @@ export const UserProfileForm: React.FC = ({ action, size, }); }; + /** + * Asynchronously load the full list of enabled trainings to display in the drop-down select field + */ + const loadTrainings = (inputValue: string, callback: (options: Array) => void): void => { + TrainingAPI.index({ disabled: false }).then(data => { + callback(buildOptions(data)); + }).catch(error => onError(error)); + }; + + /** + * Asynchronously load the full list of tags to display in the drop-down select field + */ + const loadTags = (inputValue: string, callback: (options: Array) => void): void => { + TagAPI.index().then(data => { + callback(buildOptions(data)); + }).catch(error => onError(error)); + }; + /** * Callback triggered when the form is submitted: process with the user creation or update. */ @@ -344,14 +350,14 @@ export const UserProfileForm: React.FC = ({ action, size, } {showTrainingsInput &&
} {showTagsInput &&
diff --git a/app/frontend/src/javascript/controllers/admin/invoices.js b/app/frontend/src/javascript/controllers/admin/invoices.js index 2abcaa1dd..4fb5cf9fd 100644 --- a/app/frontend/src/javascript/controllers/admin/invoices.js +++ b/app/frontend/src/javascript/controllers/admin/invoices.js @@ -530,6 +530,13 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I $scope.cancel = function () { $uibModalInstance.dismiss('cancel'); }; + $scope.rateValue = function (value) { + if (value.rate === 'null' || value.value === 'undefined' || value.rate === 'NaN') { + return ''; + } + return value.rate; + }; + const initialize = function () { rateHistory.setting.history.forEach(function (rate) { $scope.history.push({ date: rate.created_at, rate: rate.value, user: rate.user }); @@ -544,7 +551,8 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I }); return editMultiVATModalInstance.result.then(function (result) { ['Machine', 'Space', 'Training', 'Event', 'Subscription'].forEach(rateType => { - Setting.update({ name: `invoice_VAT-rate_${rateType}` }, { value: result.multiVAT[`rate${rateType}`] + '' }, function (data) { + const value = _.isFinite(result.multiVAT[`rate${rateType}`]) ? result.multiVAT[`rate${rateType}`] + '' : ''; + Setting.update({ name: `invoice_VAT-rate_${rateType}` }, { value }, function (data) { return growl.success(_t('app.admin.invoices.VAT_rate_successfully_saved')); } , function (error) { diff --git a/app/frontend/src/javascript/controllers/application.js b/app/frontend/src/javascript/controllers/application.js index 6ae830863..723c4dcbb 100644 --- a/app/frontend/src/javascript/controllers/application.js +++ b/app/frontend/src/javascript/controllers/application.js @@ -432,7 +432,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco size: 'sm', resolve: { settingsPromise: ['Setting', function (Setting) { - return Setting.query({ names: "['confirmation_required']" }).$promise; + return Setting.query({ names: "['confirmation_required', 'public_registrations']" }).$promise; }] }, controller: ['$scope', '$uibModalInstance', '_t', 'settingsPromise', function ($scope, $uibModalInstance, _t, settingsPromise) { @@ -441,6 +441,9 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco // email confirmation required before user sign-in? $scope.confirmationRequired = settingsPromise.confirmation_required; + // is public registrations allowed? + $scope.publicRegistrations = (settingsPromise.public_registrations !== 'false'); + $scope.login = function () { Auth.login(user).then(function (user) { // Authentication succeeded ... diff --git a/app/frontend/src/javascript/controllers/calendar.js b/app/frontend/src/javascript/controllers/calendar.js index 8dd82c723..9133c5bfb 100644 --- a/app/frontend/src/javascript/controllers/calendar.js +++ b/app/frontend/src/javascript/controllers/calendar.js @@ -166,9 +166,9 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ $scope.calendarConfig = CalendarConfig({ slotEventOverlap: true, header: { - left: 'month agendaWeek agendaDay', + left: 'month agendaWeek agendaDay today prev,next', center: 'title', - right: 'today prev,next' + right: '' }, minTime: moment.duration(moment(bookingWindowStart.setting.value).format('HH:mm:ss')), maxTime: moment.duration(moment(bookingWindowEnd.setting.value).format('HH:mm:ss')), diff --git a/app/frontend/src/javascript/directives/members.js b/app/frontend/src/javascript/directives/members.js index 0835f6a20..01beb32b8 100644 --- a/app/frontend/src/javascript/directives/members.js +++ b/app/frontend/src/javascript/directives/members.js @@ -1,7 +1,12 @@ -Application.Directives.directive('members', ['Member', - function (Member) { +Application.Directives.directive('members', ['Member', 'Setting', + function (Member, Setting) { return ({ restrict: 'E', + resolve: { + settingsPromise: ['Setting', function (Setting) { + return Setting.query({ names: "['public_registrations']" }).$promise; + }] + }, templateUrl: '/home/members.html', link ($scope, element, attributes) { // The last registered members who confirmed their addresses @@ -12,6 +17,10 @@ Application.Directives.directive('members', ['Member', Member.lastSubscribed({ limit: 4 }, function (data) { $scope.lastMembers = data; }); + Setting.query({ names: "['public_registrations']" }, function (data) { + // is public registrations allowed? + $scope.publicRegistrations = (data.public_registrations !== 'false'); + }); }; // !!! MUST BE CALLED AT THE END of the directive diff --git a/app/frontend/src/javascript/models/authentication-provider.ts b/app/frontend/src/javascript/models/authentication-provider.ts index c13f46fe4..bf834867a 100644 --- a/app/frontend/src/javascript/models/authentication-provider.ts +++ b/app/frontend/src/javascript/models/authentication-provider.ts @@ -47,7 +47,7 @@ export interface OpenIdConnectProvider { issuer: string, discovery: boolean, client_auth_method?: 'basic' | 'jwks', - scope?: string, + scope?: Array, prompt?: 'none' | 'login' | 'consent' | 'select_account', send_scope_to_token_endpoint?: string, client__identifier: string, diff --git a/app/frontend/src/javascript/models/user.ts b/app/frontend/src/javascript/models/user.ts index 3f59fbf7c..d2b3a1c0b 100644 --- a/app/frontend/src/javascript/models/user.ts +++ b/app/frontend/src/javascript/models/user.ts @@ -53,12 +53,14 @@ export interface User { address: string } }, - user_profile_custom_fields_attributes: { - id: number, - value: string, - invoicing_profile_id: number, - profile_custom_field_id: number - } + user_profile_custom_fields_attributes: Array< + { + id?: number, + value: string, + invoicing_profile_id: number, + profile_custom_field_id: number + } + > }, statistic_profile_attributes: { id: number, @@ -84,7 +86,8 @@ export interface User { training_credits: Array, machine_credits: Array<{ machine_id: number, hours_used: number }>, last_sign_in_at: TDateISO - validated_at: TDateISO + validated_at: TDateISO, + tag_ids: Array } type OrderingKey = 'last_name' | 'first_name' | 'email' | 'phone' | 'group' | 'plan' | 'id' diff --git a/app/frontend/src/javascript/services/calendar.js b/app/frontend/src/javascript/services/calendar.js index 02920a739..5d07e2252 100644 --- a/app/frontend/src/javascript/services/calendar.js +++ b/app/frontend/src/javascript/services/calendar.js @@ -13,9 +13,9 @@ Application.Services.factory('CalendarConfig', [() => timezone: Fablab.timezone, locale: Fablab.fullcalendar_locale, header: { - left: 'month agendaWeek', + left: 'month agendaWeek today prev,next', center: 'title', - right: 'today prev,next' + right: '' }, firstDay: Fablab.weekStartingDay, scrollTime: DEFAULT_CALENDAR_POSITION, diff --git a/app/frontend/src/stylesheets/app.plugins.scss b/app/frontend/src/stylesheets/app.plugins.scss index 9e882920b..f60fc7345 100644 --- a/app/frontend/src/stylesheets/app.plugins.scss +++ b/app/frontend/src/stylesheets/app.plugins.scss @@ -55,10 +55,14 @@ box-shadow: none; text-shadow: none; margin: 0; + margin-right: 0.5rem; height: 40px; line-height: 18px; padding: 10px; } +.fc-button-group .fc-button { + margin: 0; +} .fc-toolbar h2 { font-size: 15px; diff --git a/app/frontend/templates/admin/calendar/calendar.html b/app/frontend/templates/admin/calendar/calendar.html index 13edb10b8..821c70b40 100644 --- a/app/frontend/templates/admin/calendar/calendar.html +++ b/app/frontend/templates/admin/calendar/calendar.html @@ -77,7 +77,7 @@
-
+

{{ 'app.admin.calendar.ongoing_reservations' }}

@@ -96,7 +96,7 @@
-
+

{{ 'app.admin.calendar.machines' }}

@@ -110,7 +110,7 @@
-
+

{{ 'app.admin.calendar.plans' }}

@@ -129,7 +129,7 @@
-
+

{{ 'app.admin.calendar.actions' }}

diff --git a/app/frontend/templates/admin/invoices/settings/multiVATHistory.html b/app/frontend/templates/admin/invoices/settings/multiVATHistory.html index 77f7e2e1a..c1b669438 100644 --- a/app/frontend/templates/admin/invoices/settings/multiVATHistory.html +++ b/app/frontend/templates/admin/invoices/settings/multiVATHistory.html @@ -17,7 +17,7 @@ {{'app.admin.invoices.VAT_disabled'}} {{'app.admin.invoices.VAT_enabled'}} - {{value.rate}} + {{rateValue(value)}} {{value.date | amDateFormat:'L LT'}} {{value.user.name}}{{ 'app.admin.invoices.deleted_user' }} diff --git a/app/frontend/templates/home/members.html b/app/frontend/templates/home/members.html index 3b14722c6..e39df627d 100644 --- a/app/frontend/templates/home/members.html +++ b/app/frontend/templates/home/members.html @@ -17,7 +17,7 @@
-
+
diff --git a/app/frontend/templates/shared/deviseModal.html b/app/frontend/templates/shared/deviseModal.html index 457aeda09..a2a58a5b1 100644 --- a/app/frontend/templates/shared/deviseModal.html +++ b/app/frontend/templates/shared/deviseModal.html @@ -60,7 +60,7 @@
-

+

{{ 'app.public.common.not_registered_to_the_fablab' }}
{{ 'app.public.common.create_an_account' }}
diff --git a/app/frontend/templates/shared/signupModal.html b/app/frontend/templates/shared/signupModal.html index ada6cd95e..7dbb9ce5d 100644 --- a/app/frontend/templates/shared/signupModal.html +++ b/app/frontend/templates/shared/signupModal.html @@ -165,8 +165,8 @@ -

-
+
+
diff --git a/app/models/open_id_connect_provider.rb b/app/models/open_id_connect_provider.rb index ccf2fee5d..c9db5020e 100644 --- a/app/models/open_id_connect_provider.rb +++ b/app/models/open_id_connect_provider.rb @@ -17,6 +17,10 @@ class OpenIdConnectProvider < ApplicationRecord validates :prompt, inclusion: { in: %w[none login consent select_account], allow_nil: true } validates :client_auth_method, inclusion: { in: %w[basic jwks] } + def scope + self[:scope].join(' ') + end + def config OpenIdConnectProvider.columns.map(&:name).filter { |n| !n.start_with?('client__') && n != 'profile_url' }.map do |n| [n, send(n)] diff --git a/app/services/auth_provider_service.rb b/app/services/auth_provider_service.rb index 2370f8e1e..a1b81446d 100644 --- a/app/services/auth_provider_service.rb +++ b/app/services/auth_provider_service.rb @@ -22,7 +22,7 @@ class AuthProviderService provider.providable.response_type = 'code' provider.providable.uid_field = provider.auth_provider_mappings .find { |m| m.local_model == 'user' && m.local_field == 'uid' } - .api_field + &.api_field URI.parse(provider.providable.issuer).tap do |uri| provider.providable.client__scheme = uri.scheme diff --git a/app/services/members/list_service.rb b/app/services/members/list_service.rb index 4f2f6c8ab..0f9c4c80f 100644 --- a/app/services/members/list_service.rb +++ b/app/services/members/list_service.rb @@ -50,7 +50,7 @@ class Members::ListService 'SELECT max("created_at") ' \ 'FROM "subscriptions" ' \ 'WHERE "statistic_profile_id" = "statistic_profiles"."id")') - .where("users.is_active = 'true' AND (roles.name = 'member' OR roles.name = 'manager')") + .where("users.is_active = 'true'") .limit(50) query.downcase.split(' ').each do |word| members = members.where('lower(f_unaccent(profiles.first_name)) ~ :search OR ' \ diff --git a/app/services/vat_history_service.rb b/app/services/vat_history_service.rb index a3f202650..479c96dcc 100644 --- a/app/services/vat_history_service.rb +++ b/app/services/vat_history_service.rb @@ -72,7 +72,7 @@ class VatHistoryService .history_values.where('created_at >= ?', first_vat_rate_by_type.created_at) .order(created_at: 'ASC') vat_rate_by_type.each do |rate| - if rate.value.blank? + if rate.value.blank? || rate.value == 'null' || rate.value == 'undefined' || rate.value == 'NaN' # if, at some point in the history, a blank rate was set, the general VAT rate is used instead vat_rate = Setting.find_by(name: 'invoice_VAT-rate') .history_values.where('created_at < ?', rate.created_at) diff --git a/config/environments/production.rb b/config/environments/production.rb index 3d91fb921..8cd969738 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -77,7 +77,9 @@ Rails.application.configure do authentication: Rails.application.secrets.smtp_authentication, enable_starttls_auto: Rails.application.secrets.smtp_enable_starttls_auto, openssl_verify_mode: Rails.application.secrets.smtp_openssl_verify_mode, - tls: Rails.application.secrets.smtp_tls + tls: Rails.application.secrets.smtp_tls, + ca_file: Rails.application.secrets.smtp_ca_file, + ca_path: Rails.application.secrets.smtp_ca_path } # use :smtp for switch prod config.action_mailer.delivery_method = Rails.application.secrets.delivery_method.to_sym diff --git a/config/locales/app.shared.en.yml b/config/locales/app.shared.en.yml index 2fee9db10..2e7ef756e 100644 --- a/config/locales/app.shared.en.yml +++ b/config/locales/app.shared.en.yml @@ -527,3 +527,5 @@ en: payzen_card_update_modal: update_card: "Update the card" validate_button: "Validate the new card" + form_multi_select: + create_label: "Add {VALUE}" \ No newline at end of file diff --git a/config/locales/app.shared.fr.yml b/config/locales/app.shared.fr.yml index 37468ea44..a10fc465a 100644 --- a/config/locales/app.shared.fr.yml +++ b/config/locales/app.shared.fr.yml @@ -154,7 +154,7 @@ fr: member_select: select_a_member: "Sélectionnez un membre" start_typing: "Commencez à écrire..." - member_not_validated: "Attention :
Le membre n'a pas validé." + member_not_validated: "Attention :
Le compte du membre n'est pas validé." #payment modal payment: online_payment: "Paiement en ligne" diff --git a/config/secrets.yml b/config/secrets.yml index 5dbf1a2e7..39828e1f7 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -137,6 +137,8 @@ production: smtp_enable_starttls_auto: <%= ENV["SMTP_ENABLE_STARTTLS_AUTO"] %> smtp_openssl_verify_mode: <%= ENV["SMTP_OPENSSL_VERIFY_MODE"] %> smtp_tls: <%= ENV["SMTP_TLS"] %> + smtp_ca_file: <%= ENV.fetch("SMTP_CA_FILE", nil) %> + smtp_ca_path: <%= ENV.fetch("SMTP_CA_PATH", nil) %> week_starting_day: <%= ENV["WEEK_STARTING_DAY"] %> d3_date_format: <%= ENV.fetch("D3_DATE_FORMAT", '%y-%m-%d').dump %> uib_date_format: <%= ENV["UIB_DATE_FORMAT"] %> diff --git a/db/migrate/20220531160223_change_oidc_scope_to_array.rb b/db/migrate/20220531160223_change_oidc_scope_to_array.rb new file mode 100644 index 000000000..323317c01 --- /dev/null +++ b/db/migrate/20220531160223_change_oidc_scope_to_array.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# Previously, the OpenID Connect scope was a string, scopes were separated by commas. +# To be more fron-end friendly, we now use an array. +class ChangeOidcScopeToArray < ActiveRecord::Migration[5.2] + def change + change_column :open_id_connect_providers, :scope, "varchar[] USING (string_to_array(scope, ','))" + end +end diff --git a/db/schema.rb b/db/schema.rb index b49a86421..55a7cad70 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_05_17_140916) do +ActiveRecord::Schema.define(version: 2022_05_31_160223) do # These are extensions that must be enabled in order to support this database enable_extension "fuzzystrmatch" @@ -19,8 +19,8 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do enable_extension "unaccent" create_table "abuses", id: :serial, force: :cascade do |t| - t.string "signaled_type" t.integer "signaled_id" + t.string "signaled_type" t.string "first_name" t.string "last_name" t.string "email" @@ -49,8 +49,8 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do t.string "locality" t.string "country" t.string "postal_code" - t.string "placeable_type" t.integer "placeable_id" + t.string "placeable_type" t.datetime "created_at" t.datetime "updated_at" end @@ -64,8 +64,8 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do end create_table "assets", id: :serial, force: :cascade do |t| - t.string "viewable_type" t.integer "viewable_id" + t.string "viewable_type" t.string "attachment" t.string "type" t.datetime "created_at" @@ -146,8 +146,8 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do end create_table "credits", id: :serial, force: :cascade do |t| - t.string "creditable_type" t.integer "creditable_id" + t.string "creditable_type" t.integer "plan_id" t.integer "hours" t.datetime "created_at" @@ -369,15 +369,15 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do create_table "notifications", id: :serial, force: :cascade do |t| t.integer "receiver_id" - t.string "attached_object_type" t.integer "attached_object_id" + t.string "attached_object_type" t.integer "notification_type_id" t.boolean "is_read", default: false t.datetime "created_at" t.datetime "updated_at" t.string "receiver_type" t.boolean "is_send", default: false - t.jsonb "meta_data", default: "{}" + t.jsonb "meta_data", default: {} t.index ["notification_type_id"], name: "index_notifications_on_notification_type_id" t.index ["receiver_id"], name: "index_notifications_on_receiver_id" end @@ -415,7 +415,7 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do t.string "issuer" t.boolean "discovery" t.string "client_auth_method" - t.string "scope" + t.string "scope", array: true t.string "response_type" t.string "response_mode" t.string "display" @@ -570,8 +570,8 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do create_table "prices", id: :serial, force: :cascade do |t| t.integer "group_id" t.integer "plan_id" - t.string "priceable_type" t.integer "priceable_id" + t.string "priceable_type" t.integer "amount" t.datetime "created_at", null: false t.datetime "updated_at", null: false @@ -729,8 +729,8 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do t.text "message" t.datetime "created_at" t.datetime "updated_at" - t.string "reservable_type" t.integer "reservable_id" + t.string "reservable_type" t.integer "nb_reserve_places" t.integer "statistic_profile_id" t.index ["reservable_type", "reservable_id"], name: "index_reservations_on_reservable_type_and_reservable_id" @@ -739,8 +739,8 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do create_table "roles", id: :serial, force: :cascade do |t| t.string "name" - t.string "resource_type" t.integer "resource_id" + t.string "resource_type" t.datetime "created_at" t.datetime "updated_at" t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id" @@ -1021,8 +1021,8 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do t.boolean "is_allow_newsletter" t.inet "current_sign_in_ip" t.inet "last_sign_in_ip" - t.datetime "validated_at" t.string "mapped_from_sso" + t.datetime "validated_at" t.index ["auth_token"], name: "index_users_on_auth_token" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true diff --git a/doc/environment.md b/doc/environment.md index 63f1a21e0..ec9577d8b 100644 --- a/doc/environment.md +++ b/doc/environment.md @@ -64,6 +64,26 @@ See http://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-config When DELIVERY_METHOD is set to **smtp**, configure the SMTP server parameters. See https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration for more details. + + + SMTP_CA_FILE + +The path to a file containing a PEM-format CA certificate. +See [OpenSSL::SSL::SSLContext](https://ruby-doc.org/stdlib-2.6/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html). +This is an undocumented setting of [mail gem](https://github.com/mikel/mail). +In production with Docker, you may need to mount your certificate into the running container, by adding an entry in your [docker-compose.yml](https://github.com/sleede/fab-manager/blob/master/setup/docker-compose.yml), under `services > fabmanager > volumes`, like the following: `- ${PWD}/certs/ca-cert-file.pem:/etc/ssl/my-certs/ca-cert-file.pem` + + + SMTP_CA_PATH + +The path to a directory containing CA certificates in PEM format. +See [OpenSSL::SSL::SSLContext](https://ruby-doc.org/stdlib-2.6/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html). +This is an undocumented setting of [mail gem](https://github.com/mikel/mail). +In production with Docker, you may need to mount your certificate into the running container, by adding an entry in your [docker-compose.yml](https://github.com/sleede/fab-manager/blob/master/setup/docker-compose.yml), under `services > fabmanager > volumes`, like the following: `- ${PWD}/certs:/etc/ssl/my-certs` + + + SMTP_TLS +Enables the SMTP connection to use SMTP/TLS (SMTPS: SMTP over direct TLS connection). DEFAULT_HOST, DEFAULT_PROTOCOL diff --git a/package.json b/package.json index 4ed7b041d..9953a6f6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fab-manager", - "version": "5.4.1", + "version": "5.4.2", "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": [ "fablab", @@ -137,7 +137,7 @@ "react-hook-form": "^7.30.0", "react-i18next": "^11.15.6", "react-modal": "^3.11.2", - "react-select": "^5.2.2", + "react-select": "^5.3.2", "react-switch": "^6.0.0", "react2angular": "^4.0.6", "resolve-url-loader": "^4.0.0", diff --git a/scripts/pg-analyzers.sh b/scripts/pg-analyzers.sh index 3d2861859..7cfc7bcd1 100755 --- a/scripts/pg-analyzers.sh +++ b/scripts/pg-analyzers.sh @@ -4,11 +4,11 @@ docker-compose() { if ! docker compose version 1>/dev/null 2>/dev/null then - if ! \docker-compose version 1>/dev/null 2>/dev/null + if ! command docker-compose version 1>/dev/null 2>/dev/null then echo -e "\e[91m[ ❌ ] docker-compose was not found, exiting...\e[39m" && exit 1 else - \docker-compose "$@" + command docker-compose "$@" fi else docker compose "$@" diff --git a/scripts/run.sh b/scripts/run.sh index 09e49c9ce..2d145b91c 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -12,11 +12,11 @@ docker-compose() { if ! docker compose version 1>/dev/null 2>/dev/null then - if ! \docker-compose version 1>/dev/null 2>/dev/null + if ! command docker-compose version 1>/dev/null 2>/dev/null then echo -e "\e[91m[ ❌ ] docker-compose was not found, exiting...\e[39m" && exit 1 else - \docker-compose "$@" + command docker-compose "$@" fi else docker compose "$@" diff --git a/setup/setup.sh b/setup/setup.sh index 226ed8536..bae810904 100755 --- a/setup/setup.sh +++ b/setup/setup.sh @@ -63,11 +63,11 @@ docker-compose() { if ! docker compose version 1>/dev/null 2>/dev/null then - if ! \docker-compose version 1>/dev/null 2>/dev/null + if ! command docker-compose version 1>/dev/null 2>/dev/null then echo -e "\e[91m[ ❌ ] docker-compose was not found, exiting...\e[39m" && exit 1 else - \docker-compose "$@" + command docker-compose "$@" fi else docker compose "$@" diff --git a/setup/upgrade.sh b/setup/upgrade.sh index f3e48e451..fbabdab62 100644 --- a/setup/upgrade.sh +++ b/setup/upgrade.sh @@ -47,11 +47,11 @@ docker-compose() { if ! docker compose version 1>/dev/null 2>/dev/null then - if ! \docker-compose version 1>/dev/null 2>/dev/null + if ! command docker-compose version 1>/dev/null 2>/dev/null then echo -e "\e[91m[ ❌ ] docker-compose was not found, exiting...\e[39m" && exit 1 else - \docker-compose "$@" + command docker-compose "$@" fi else docker compose "$@" diff --git a/yarn.lock b/yarn.lock index d7bc10bfc..463c8cbdd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -370,6 +370,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5" integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== +"@babel/helper-plugin-utils@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96" + integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA== + "@babel/helper-remap-async-to-generator@^7.16.8": version "7.16.8" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz#29ffaade68a367e2ed09c90901986918d25e57e3" @@ -719,6 +724,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" +"@babel/plugin-syntax-jsx@^7.12.13": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.17.12.tgz#834035b45061983a491f60096f61a2e7c5674a47" + integrity sha512-spyY3E3AURfxh/RHtjx5j6hs8am5NbUBGfcZ2vB3uShSpZdQyXSf5rR5Mk76vbtlAZOelyVQ71Fg0x9SG4fsog== + dependencies: + "@babel/helper-plugin-utils" "^7.17.12" + "@babel/plugin-syntax-jsx@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz#50b6571d13f764266a113d77c82b4a6508bbe665" @@ -1370,6 +1382,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.7.2": + version "7.18.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.3.tgz#c7b654b57f6f63cf7f8b418ac9ca04408c4579f4" + integrity sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4" @@ -1469,6 +1488,24 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f" integrity sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA== +"@emotion/babel-plugin@^11.7.1": + version "11.9.2" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz#723b6d394c89fb2ef782229d92ba95a740576e95" + integrity sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw== + dependencies: + "@babel/helper-module-imports" "^7.12.13" + "@babel/plugin-syntax-jsx" "^7.12.13" + "@babel/runtime" "^7.13.10" + "@emotion/hash" "^0.8.0" + "@emotion/memoize" "^0.7.5" + "@emotion/serialize" "^1.0.2" + babel-plugin-macros "^2.6.1" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.0.13" + "@emotion/cache@^11.4.0": version "11.4.0" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.4.0.tgz#293fc9d9a7a38b9aad8e9337e5014366c3b09ac0" @@ -1480,26 +1517,37 @@ "@emotion/weak-memoize" "^0.2.5" stylis "^4.0.3" +"@emotion/cache@^11.7.1": + version "11.7.1" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.7.1.tgz#08d080e396a42e0037848214e8aa7bf879065539" + integrity sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A== + dependencies: + "@emotion/memoize" "^0.7.4" + "@emotion/sheet" "^1.1.0" + "@emotion/utils" "^1.0.0" + "@emotion/weak-memoize" "^0.2.5" + stylis "4.0.13" + "@emotion/hash@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== -"@emotion/memoize@^0.7.4": +"@emotion/memoize@^0.7.4", "@emotion/memoize@^0.7.5": version "0.7.5" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50" integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ== -"@emotion/react@^11.1.1": - version "11.4.0" - resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.4.0.tgz#2465ad7b073a691409b88dfd96dc17097ddad9b7" - integrity sha512-4XklWsl9BdtatLoJpSjusXhpKv9YVteYKh9hPKP1Sxl+mswEFoUe0WtmtWjxEjkA51DQ2QRMCNOvKcSlCQ7ivg== +"@emotion/react@^11.8.1": + version "11.9.0" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.9.0.tgz#b6d42b1db3bd7511e7a7c4151dc8bc82e14593b8" + integrity sha512-lBVSF5d0ceKtfKCDQJveNAtkC7ayxpVlgOohLgXqRwqWr9bOf4TZAFFyIcNngnV6xK6X4x2ZeXq7vliHkoVkxQ== dependencies: "@babel/runtime" "^7.13.10" - "@emotion/cache" "^11.4.0" - "@emotion/serialize" "^1.0.2" - "@emotion/sheet" "^1.0.1" - "@emotion/utils" "^1.0.0" + "@emotion/babel-plugin" "^11.7.1" + "@emotion/cache" "^11.7.1" + "@emotion/serialize" "^1.0.3" + "@emotion/utils" "^1.1.0" "@emotion/weak-memoize" "^0.2.5" hoist-non-react-statics "^3.3.1" @@ -1514,11 +1562,27 @@ "@emotion/utils" "^1.0.0" csstype "^3.0.2" -"@emotion/sheet@^1.0.0", "@emotion/sheet@^1.0.1": +"@emotion/serialize@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.3.tgz#99e2060c26c6292469fb30db41f4690e1c8fea63" + integrity sha512-2mSSvgLfyV3q+iVh3YWgNlUc2a9ZlDU7DjuP5MjK3AXRR0dYigCrP99aeFtaB2L/hjfEZdSThn5dsZ0ufqbvsA== + dependencies: + "@emotion/hash" "^0.8.0" + "@emotion/memoize" "^0.7.4" + "@emotion/unitless" "^0.7.5" + "@emotion/utils" "^1.0.0" + csstype "^3.0.2" + +"@emotion/sheet@^1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.0.1.tgz#245f54abb02dfd82326e28689f34c27aa9b2a698" integrity sha512-GbIvVMe4U+Zc+929N1V7nW6YYJtidj31lidSmdYcWozwoBIObXBnaJkKNDjZrLm9Nc0BR+ZyHNaRZxqNZbof5g== +"@emotion/sheet@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.1.0.tgz#56d99c41f0a1cda2726a05aa6a20afd4c63e58d2" + integrity sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g== + "@emotion/unitless@^0.7.5": version "0.7.5" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" @@ -1529,6 +1593,11 @@ resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.0.0.tgz#abe06a83160b10570816c913990245813a2fd6af" integrity sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA== +"@emotion/utils@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.1.0.tgz#86b0b297f3f1a0f2bdb08eeac9a2f49afd40d0cf" + integrity sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ== + "@emotion/weak-memoize@^0.2.5": version "0.2.5" resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" @@ -2965,6 +3034,15 @@ babel-plugin-dynamic-import-node@^2.3.3: dependencies: object.assign "^4.1.0" +babel-plugin-macros@^2.6.1: + version "2.8.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" + integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== + dependencies: + "@babel/runtime" "^7.7.2" + cosmiconfig "^6.0.0" + resolve "^1.12.0" + babel-plugin-macros@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" @@ -3379,7 +3457,7 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -convert-source-map@^1.7.0: +convert-source-map@^1.5.0, convert-source-map@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== @@ -3427,6 +3505,17 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +cosmiconfig@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" + integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.7.2" + cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" @@ -4348,6 +4437,11 @@ find-cache-dir@^3.3.1: make-dir "^3.0.2" pkg-dir "^4.1.0" +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" @@ -4794,7 +4888,7 @@ immutable@^4.0.0: resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23" integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw== -import-fresh@^3.0.0, import-fresh@^3.2.1: +import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -6486,14 +6580,14 @@ react-refresh@^0.11.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== -react-select@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.2.2.tgz#3d5edf0a60f1276fd5f29f9f90a305f0a25a5189" - integrity sha512-miGS2rT1XbFNjduMZT+V73xbJEeMzVkJOz727F6MeAr2hKE0uUSA8Ff7vD44H32x2PD3SRB6OXTY/L+fTV3z9w== +react-select@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.3.2.tgz#ecee0d5c59ed4acb7f567f7de3c75a488d93dacb" + integrity sha512-W6Irh7U6Ha7p5uQQ2ZnemoCQ8mcfgOtHfw3wuMzG6FAu0P+CYicgofSLOq97BhjMx8jS+h+wwWdCBeVVZ9VqlQ== dependencies: "@babel/runtime" "^7.12.0" "@emotion/cache" "^11.4.0" - "@emotion/react" "^11.1.1" + "@emotion/react" "^11.8.1" "@types/react-transition-group" "^4.4.0" memoize-one "^5.0.0" prop-types "^15.6.0" @@ -6738,7 +6832,7 @@ resolve@^1.10.1, resolve@^1.14.2: is-core-module "^2.2.0" path-parse "^1.0.6" -resolve@^1.19.0, resolve@^1.20.0, resolve@^1.9.0: +resolve@^1.12.0, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.9.0: version "1.22.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== @@ -7019,7 +7113,7 @@ source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, sourc resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.5.0: +source-map@^0.5.0, source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -7153,6 +7247,11 @@ stylehacks@^5.1.0: browserslist "^4.16.6" postcss-selector-parser "^6.0.4" +stylis@4.0.13: + version "4.0.13" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91" + integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag== + stylis@^4.0.3: version "4.0.10" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.10.tgz#446512d1097197ab3f02fb3c258358c3f7a14240" @@ -7710,7 +7809,7 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.10.0, yaml@^1.10.2: +yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==