mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-18 07:52:23 +01:00
(bug) show error validation message
This commit is contained in:
parent
5658e0aeed
commit
4dde127203
@ -24,9 +24,10 @@ export const AdvancedAccountingForm = <TFieldValues extends FieldValues>({ regis
|
||||
}, []);
|
||||
|
||||
return (<>
|
||||
<p>toto</p>
|
||||
{isEnabled && <>
|
||||
<header>
|
||||
<p className="title">{t('app.admin.advanced_accounting_form.title')}</p>
|
||||
<p className="title" role="heading">{t('app.admin.advanced_accounting_form.title')}</p>
|
||||
</header>
|
||||
<div className="content">
|
||||
<FormInput register={register}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { UseFormRegister } from 'react-hook-form';
|
||||
import { UseFormRegister, FormState } from 'react-hook-form';
|
||||
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FormInput } from '../form/form-input';
|
||||
@ -6,12 +6,13 @@ import { FormInput } from '../form/form-input';
|
||||
export interface BooleanMappingFormProps<TFieldValues> {
|
||||
register: UseFormRegister<TFieldValues>,
|
||||
fieldMappingId: number,
|
||||
formState: FormState<TFieldValues>
|
||||
}
|
||||
|
||||
/**
|
||||
* Partial form to map an internal boolean field to an external API providing a string value.
|
||||
*/
|
||||
export const BooleanMappingForm = <TFieldValues extends FieldValues>({ register, fieldMappingId }: BooleanMappingFormProps<TFieldValues>) => {
|
||||
export const BooleanMappingForm = <TFieldValues extends FieldValues>({ register, fieldMappingId, formState }: BooleanMappingFormProps<TFieldValues>) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
return (
|
||||
@ -20,10 +21,12 @@ export const BooleanMappingForm = <TFieldValues extends FieldValues>({ register,
|
||||
<FormInput id={`auth_provider_mappings_attributes.${fieldMappingId}.transformation.true_value`}
|
||||
register={register}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
label={t('app.admin.authentication.boolean_mapping_form.true_value')} />
|
||||
<FormInput id={`auth_provider_mappings_attributes.${fieldMappingId}.transformation.false_value`}
|
||||
register={register}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
label={t('app.admin.authentication.boolean_mapping_form.false_value')} />
|
||||
</div>
|
||||
);
|
||||
|
@ -3,7 +3,7 @@ import { UseFormRegister, useFieldArray, ArrayPath, useWatch, Path, FieldPathVal
|
||||
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||
import AuthProviderAPI from '../../api/auth-provider';
|
||||
import { AuthenticationProviderMapping, MappingFields, mappingType, ProvidableType } from '../../models/authentication-provider';
|
||||
import { Control, UnpackNestedValue, UseFormSetValue } from 'react-hook-form/dist/types/form';
|
||||
import { Control, UnpackNestedValue, UseFormSetValue, FormState } from 'react-hook-form/dist/types/form';
|
||||
import { FormSelect } from '../form/form-select';
|
||||
import { FormInput } from '../form/form-input';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -19,6 +19,7 @@ export interface DataMappingFormProps<TFieldValues, TContext extends object> {
|
||||
providerType: ProvidableType,
|
||||
setValue: UseFormSetValue<TFieldValues>,
|
||||
currentFormValues: Array<AuthenticationProviderMapping>,
|
||||
formState: FormState<TFieldValues>
|
||||
}
|
||||
|
||||
type selectModelFieldOption = { value: string, label: string };
|
||||
@ -26,7 +27,7 @@ type selectModelFieldOption = { value: string, label: string };
|
||||
/**
|
||||
* Partial form to define the mapping of the data between the API of the authentication provider and the application internals.
|
||||
*/
|
||||
export const DataMappingForm = <TFieldValues extends FieldValues, TContext extends object>({ register, control, providerType, setValue, currentFormValues }: DataMappingFormProps<TFieldValues, TContext>) => {
|
||||
export const DataMappingForm = <TFieldValues extends FieldValues, TContext extends object>({ register, control, providerType, setValue, currentFormValues, formState }: DataMappingFormProps<TFieldValues, TContext>) => {
|
||||
const { t } = useTranslation('admin');
|
||||
const [dataMapping, setDataMapping] = useState<MappingFields>(null);
|
||||
const [isOpenTypeMappingModal, updateIsOpenTypeMappingModal] = useImmer<Map<number, boolean>>(new Map());
|
||||
@ -144,20 +145,24 @@ export const DataMappingForm = <TFieldValues extends FieldValues, TContext exten
|
||||
<FormInput id={`auth_provider_mappings_attributes.${index}.id`} register={register} type="hidden" />
|
||||
<div className="local-data">
|
||||
<FormSelect id={`auth_provider_mappings_attributes.${index}.local_model`}
|
||||
control={control} rules={{ required: true }}
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
options={buildModelOptions()}
|
||||
label={t('app.admin.authentication.data_mapping_form.model')}/>
|
||||
<FormSelect id={`auth_provider_mappings_attributes.${index}.local_field`}
|
||||
options={buildFieldOptions(output, index)}
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
label={t('app.admin.authentication.data_mapping_form.field')} />
|
||||
</div>
|
||||
<div className="remote-data">
|
||||
{providerType === 'OAuth2Provider' && <Oauth2DataMappingForm register={register} control={control} index={index} />}
|
||||
{providerType === 'OAuth2Provider' && <Oauth2DataMappingForm register={register} control={control} index={index} formState={formState} />}
|
||||
{providerType === 'OpenIdConnectProvider' && <OpenidConnectDataMappingForm register={register}
|
||||
index={index}
|
||||
setValue={setValue}
|
||||
formState={formState}
|
||||
currentFormValues={currentFormValues} />}
|
||||
</div>
|
||||
</div>
|
||||
@ -172,7 +177,9 @@ export const DataMappingForm = <TFieldValues extends FieldValues, TContext exten
|
||||
type={getDataType(output, index)}
|
||||
isOpen={isOpenTypeMappingModal.get(index)}
|
||||
toggleModal={toggleTypeMappingModal(index)}
|
||||
control={control} register={register}
|
||||
control={control}
|
||||
register={register}
|
||||
formState={formState}
|
||||
fieldMappingId={index} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,17 +1,18 @@
|
||||
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FormSelect } from '../form/form-select';
|
||||
import { Control } from 'react-hook-form/dist/types/form';
|
||||
import { Control, FormState } from 'react-hook-form/dist/types/form';
|
||||
|
||||
export interface DateMappingFormProps<TFieldValues, TContext extends object> {
|
||||
control: Control<TFieldValues, TContext>,
|
||||
fieldMappingId: number,
|
||||
formState: FormState<TFieldValues>
|
||||
}
|
||||
|
||||
/**
|
||||
* Partial form for mapping an internal date field to an external API.
|
||||
*/
|
||||
export const DateMappingForm = <TFieldValues extends FieldValues, TContext extends object>({ control, fieldMappingId }: DateMappingFormProps<TFieldValues, TContext>) => {
|
||||
export const DateMappingForm = <TFieldValues extends FieldValues, TContext extends object>({ control, fieldMappingId, formState }: DateMappingFormProps<TFieldValues, TContext>) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
// available date formats
|
||||
@ -44,6 +45,7 @@ export const DateMappingForm = <TFieldValues extends FieldValues, TContext exten
|
||||
<FormSelect id={`auth_provider_mappings_attributes.${fieldMappingId}.transformation.format`}
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
options={dateFormats}
|
||||
label={t('app.admin.authentication.date_mapping_form.date_format')} />
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ArrayPath, useFieldArray, UseFormRegister } from 'react-hook-form';
|
||||
import { Control } from 'react-hook-form/dist/types/form';
|
||||
import { Control, FormState } from 'react-hook-form/dist/types/form';
|
||||
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
@ -9,12 +9,13 @@ export interface IntegerMappingFormProps<TFieldValues, TContext extends object>
|
||||
register: UseFormRegister<TFieldValues>,
|
||||
control: Control<TFieldValues, TContext>,
|
||||
fieldMappingId: number,
|
||||
formState: FormState<TFieldValues>
|
||||
}
|
||||
|
||||
/**
|
||||
* Partial for to map an internal integer field to an external API providing a string value.
|
||||
*/
|
||||
export const IntegerMappingForm = <TFieldValues extends FieldValues, TContext extends object>({ register, control, fieldMappingId }: IntegerMappingFormProps<TFieldValues, TContext>) => {
|
||||
export const IntegerMappingForm = <TFieldValues extends FieldValues, TContext extends object>({ register, control, fieldMappingId, formState }: IntegerMappingFormProps<TFieldValues, TContext>) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const { fields, append, remove } = useFieldArray({ control, name: 'auth_provider_mappings_attributes_transformation_mapping' as ArrayPath<TFieldValues> });
|
||||
@ -33,11 +34,13 @@ export const IntegerMappingForm = <TFieldValues extends FieldValues, TContext ex
|
||||
<FormInput id={`auth_provider_mappings_attributes.${fieldMappingId}.transformation.mapping.${index}.from`}
|
||||
register={register}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
label={t('app.admin.authentication.integer_mapping_form.mapping_from')} />
|
||||
<FormInput id={`auth_provider_mappings_attributes.${fieldMappingId}.transformation.mapping.${index}.to`}
|
||||
register={register}
|
||||
type="number"
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
label={t('app.admin.authentication.integer_mapping_form.mapping_to')} />
|
||||
</div>
|
||||
<div className="actions">
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { UseFormRegister } from 'react-hook-form';
|
||||
import { Control } from 'react-hook-form/dist/types/form';
|
||||
import { Control, FormState } from 'react-hook-form/dist/types/form';
|
||||
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||
import { FormInput } from '../form/form-input';
|
||||
import { FormSelect } from '../form/form-select';
|
||||
@ -10,13 +10,14 @@ interface Oauth2DataMappingFormProps<TFieldValues, TContext extends object> {
|
||||
register: UseFormRegister<TFieldValues>,
|
||||
control: Control<TFieldValues, TContext>,
|
||||
index: number,
|
||||
formState: FormState<TFieldValues>
|
||||
}
|
||||
|
||||
/**
|
||||
* Partial form to set the data mapping for an OAuth 2.0 provider.
|
||||
* The data mapping is the way to bind data from the authentication provider API to the Fab-manager's database
|
||||
*/
|
||||
export const Oauth2DataMappingForm = <TFieldValues extends FieldValues, TContext extends object>({ register, control, index }: Oauth2DataMappingFormProps<TFieldValues, TContext>) => {
|
||||
export const Oauth2DataMappingForm = <TFieldValues extends FieldValues, TContext extends object>({ register, control, index, formState }: Oauth2DataMappingFormProps<TFieldValues, TContext>) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
return (
|
||||
@ -24,15 +25,19 @@ export const Oauth2DataMappingForm = <TFieldValues extends FieldValues, TContext
|
||||
<FormInput id={`auth_provider_mappings_attributes.${index}.api_endpoint`}
|
||||
register={register}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
placeholder="/api/resource..."
|
||||
label={t('app.admin.authentication.oauth2_data_mapping_form.api_endpoint_url')} />
|
||||
<FormSelect id={`auth_provider_mappings_attributes.${index}.api_data_type`}
|
||||
options={[{ label: 'JSON', value: 'json' }]}
|
||||
control={control} rules={{ required: true }}
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
label={t('app.admin.authentication.oauth2_data_mapping_form.api_type')} />
|
||||
<FormInput id={`auth_provider_mappings_attributes.${index}.api_field`}
|
||||
register={register}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
placeholder="field_name..."
|
||||
tooltip={<HtmlTranslate trKey="app.admin.authentication.oauth2_data_mapping_form.api_field_help_html" />}
|
||||
label={t('app.admin.authentication.oauth2_data_mapping_form.api_field')} />
|
||||
|
@ -1,18 +1,19 @@
|
||||
import { FormInput } from '../form/form-input';
|
||||
import { UseFormRegister } from 'react-hook-form';
|
||||
import { UseFormRegister, FormState } from 'react-hook-form';
|
||||
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FabOutputCopy } from '../base/fab-output-copy';
|
||||
|
||||
interface Oauth2FormProps<TFieldValues> {
|
||||
register: UseFormRegister<TFieldValues>,
|
||||
formState: FormState<TFieldValues>,
|
||||
strategyName?: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* Partial form to fill the OAuth2 settings for a new/existing authentication provider.
|
||||
*/
|
||||
export const Oauth2Form = <TFieldValues extends FieldValues>({ register, strategyName }: Oauth2FormProps<TFieldValues>) => {
|
||||
export const Oauth2Form = <TFieldValues extends FieldValues>({ register, strategyName, formState }: Oauth2FormProps<TFieldValues>) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
// regular expression to validate the input fields
|
||||
@ -34,31 +35,37 @@ export const Oauth2Form = <TFieldValues extends FieldValues>({ register, strateg
|
||||
register={register}
|
||||
placeholder="https://sso.example.net..."
|
||||
label={t('app.admin.authentication.oauth2_form.common_url')}
|
||||
rules={{ required: true, pattern: urlRegex }} />
|
||||
rules={{ required: true, pattern: urlRegex }}
|
||||
formState={formState} />
|
||||
<FormInput id="providable_attributes.authorization_endpoint"
|
||||
register={register}
|
||||
placeholder="/oauth2/auth..."
|
||||
label={t('app.admin.authentication.oauth2_form.authorization_endpoint')}
|
||||
rules={{ required: true, pattern: endpointRegex }} />
|
||||
rules={{ required: true, pattern: endpointRegex }}
|
||||
formState={formState} />
|
||||
<FormInput id="providable_attributes.token_endpoint"
|
||||
register={register}
|
||||
placeholder="/oauth2/token..."
|
||||
label={t('app.admin.authentication.oauth2_form.token_acquisition_endpoint')}
|
||||
rules={{ required: true, pattern: endpointRegex }} />
|
||||
rules={{ required: true, pattern: endpointRegex }}
|
||||
formState={formState} />
|
||||
<FormInput id="providable_attributes.profile_url"
|
||||
register={register}
|
||||
placeholder="https://exemple.net/user..."
|
||||
label={t('app.admin.authentication.oauth2_form.profile_edition_url')}
|
||||
tooltip={t('app.admin.authentication.oauth2_form.profile_edition_url_help')}
|
||||
rules={{ required: true, pattern: urlRegex }} />
|
||||
rules={{ required: true, pattern: urlRegex }}
|
||||
formState={formState} />
|
||||
<FormInput id="providable_attributes.client_id"
|
||||
register={register}
|
||||
label={t('app.admin.authentication.oauth2_form.client_identifier')}
|
||||
rules={{ required: true }} />
|
||||
rules={{ required: true }}
|
||||
formState={formState} />
|
||||
<FormInput id="providable_attributes.client_secret"
|
||||
register={register}
|
||||
label={t('app.admin.authentication.oauth2_form.client_secret')}
|
||||
rules={{ required: true }} />
|
||||
rules={{ required: true }}
|
||||
formState={formState} />
|
||||
<FormInput id="providable_attributes.scopes" register={register}
|
||||
placeholder="profile,email..."
|
||||
label={t('app.admin.authentication.oauth2_form.scopes')} />
|
||||
|
@ -3,7 +3,7 @@ import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||
import { FormInput } from '../form/form-input';
|
||||
import { HtmlTranslate } from '../base/html-translate';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { UnpackNestedValue, UseFormSetValue } from 'react-hook-form/dist/types/form';
|
||||
import { UnpackNestedValue, UseFormSetValue, FormState } from 'react-hook-form/dist/types/form';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { FieldPathValue } from 'react-hook-form/dist/types/path';
|
||||
import { AuthenticationProviderMapping } from '../../models/authentication-provider';
|
||||
@ -13,13 +13,14 @@ interface OpenidConnectDataMappingFormProps<TFieldValues> {
|
||||
setValue: UseFormSetValue<TFieldValues>,
|
||||
currentFormValues: Array<AuthenticationProviderMapping>,
|
||||
index: number,
|
||||
formState: FormState<TFieldValues>
|
||||
}
|
||||
|
||||
/**
|
||||
* Partial form to set the data mapping for an OpenID Connect provider.
|
||||
* The data mapping is the way to bind data from the OIDC claims to the Fab-manager's database
|
||||
*/
|
||||
export const OpenidConnectDataMappingForm = <TFieldValues extends FieldValues>({ register, setValue, currentFormValues, index }: OpenidConnectDataMappingFormProps<TFieldValues>) => {
|
||||
export const OpenidConnectDataMappingForm = <TFieldValues extends FieldValues>({ register, setValue, currentFormValues, index, formState }: OpenidConnectDataMappingFormProps<TFieldValues>) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const standardConfiguration = {
|
||||
@ -65,15 +66,18 @@ export const OpenidConnectDataMappingForm = <TFieldValues extends FieldValues>({
|
||||
type="hidden"
|
||||
register={register}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
defaultValue="user_info" />
|
||||
<FormInput id={`auth_provider_mappings_attributes.${index}.api_data_type`}
|
||||
type="hidden"
|
||||
register={register}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
defaultValue="json" />
|
||||
<FormInput id={`auth_provider_mappings_attributes.${index}.api_field`}
|
||||
register={register}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
placeholder="claim..."
|
||||
tooltip={<HtmlTranslate trKey="app.admin.authentication.openid_connect_data_mapping_form.api_field_help_html" />}
|
||||
label={t('app.admin.authentication.openid_connect_data_mapping_form.api_field')} />
|
||||
|
@ -161,41 +161,49 @@ export const OpenidConnectForm = <TFieldValues extends FieldValues, TContext ext
|
||||
placeholder="https://sso.exemple.com/my-account"
|
||||
label={t('app.admin.authentication.openid_connect_form.profile_edition_url')}
|
||||
tooltip={t('app.admin.authentication.openid_connect_form.profile_edition_url_help')}
|
||||
rules={{ required: false, pattern: urlRegex }} />
|
||||
rules={{ required: false, pattern: urlRegex }}
|
||||
formState={formState} />
|
||||
<h4>{t('app.admin.authentication.openid_connect_form.client_options')}</h4>
|
||||
<FormInput id="providable_attributes.client__identifier"
|
||||
label={t('app.admin.authentication.openid_connect_form.client__identifier')}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
register={register} />
|
||||
<FormInput id="providable_attributes.client__secret"
|
||||
label={t('app.admin.authentication.openid_connect_form.client__secret')}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
register={register} />
|
||||
{!currentFormValues?.discovery && <div className="client-options-without-discovery">
|
||||
<FormInput id="providable_attributes.client__authorization_endpoint"
|
||||
label={t('app.admin.authentication.openid_connect_form.client__authorization_endpoint')}
|
||||
placeholder="/authorize"
|
||||
rules={{ required: !currentFormValues?.discovery, pattern: endpointRegex }}
|
||||
formState={formState}
|
||||
register={register} />
|
||||
<FormInput id="providable_attributes.client__token_endpoint"
|
||||
label={t('app.admin.authentication.openid_connect_form.client__token_endpoint')}
|
||||
placeholder="/token"
|
||||
rules={{ required: !currentFormValues?.discovery, pattern: endpointRegex }}
|
||||
formState={formState}
|
||||
register={register} />
|
||||
<FormInput id="providable_attributes.client__userinfo_endpoint"
|
||||
label={t('app.admin.authentication.openid_connect_form.client__userinfo_endpoint')}
|
||||
placeholder="/userinfo"
|
||||
rules={{ required: !currentFormValues?.discovery, pattern: endpointRegex }}
|
||||
formState={formState}
|
||||
register={register} />
|
||||
{currentFormValues?.client_auth_method === 'jwks' && <FormInput id="providable_attributes.client__jwks_uri"
|
||||
label={t('app.admin.authentication.openid_connect_form.client__jwks_uri')}
|
||||
rules={{ required: currentFormValues.client_auth_method === 'jwks', pattern: endpointRegex }}
|
||||
formState={formState}
|
||||
placeholder="/jwk"
|
||||
register={register} />}
|
||||
<FormInput id="providable_attributes.client__end_session_endpoint"
|
||||
label={t('app.admin.authentication.openid_connect_form.client__end_session_endpoint')}
|
||||
tooltip={t('app.admin.authentication.openid_connect_form.client__end_session_endpoint_help')}
|
||||
rules={{ pattern: endpointRegex }}
|
||||
formState={formState}
|
||||
register={register} />
|
||||
</div>}
|
||||
</div>
|
||||
|
@ -99,6 +99,7 @@ export const ProviderForm: React.FC<ProviderFormProps> = ({ action, provider, on
|
||||
register={register}
|
||||
disabled={action === 'update'}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
label={t('app.admin.authentication.provider_form.name')} />
|
||||
<FormSelect id="providable_type"
|
||||
control={control}
|
||||
@ -106,9 +107,10 @@ export const ProviderForm: React.FC<ProviderFormProps> = ({ action, provider, on
|
||||
label={t('app.admin.authentication.provider_form.authentication_type')}
|
||||
onChange={onProvidableTypeChange}
|
||||
disabled={action === 'update'}
|
||||
rules={{ required: true }} />
|
||||
rules={{ required: true }}
|
||||
formState={formState} />
|
||||
{providableType === 'DatabaseProvider' && <DatabaseForm register={register} />}
|
||||
{providableType === 'OAuth2Provider' && <Oauth2Form register={register} strategyName={strategyName} />}
|
||||
{providableType === 'OAuth2Provider' && <Oauth2Form register={register} strategyName={strategyName} formState={formState} />}
|
||||
{providableType === 'OpenIdConnectProvider' && <OpenidConnectForm register={register}
|
||||
control={control}
|
||||
currentFormValues={output.providable_attributes as OpenIdConnectProvider}
|
||||
@ -116,6 +118,7 @@ export const ProviderForm: React.FC<ProviderFormProps> = ({ action, provider, on
|
||||
setValue={setValue} />}
|
||||
{providableType && providableType !== 'DatabaseProvider' && <DataMappingForm register={register}
|
||||
control={control}
|
||||
formState={formState}
|
||||
providerType={providableType}
|
||||
setValue={setValue}
|
||||
currentFormValues={output.auth_provider_mappings_attributes as Array<AuthenticationProviderMapping>} />}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ArrayPath, useFieldArray, UseFormRegister } from 'react-hook-form';
|
||||
import { Control } from 'react-hook-form/dist/types/form';
|
||||
import { Control, FormState } from 'react-hook-form/dist/types/form';
|
||||
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
@ -9,12 +9,13 @@ export interface StringMappingFormProps<TFieldValues, TContext extends object> {
|
||||
register: UseFormRegister<TFieldValues>,
|
||||
control: Control<TFieldValues, TContext>,
|
||||
fieldMappingId: number,
|
||||
formState: FormState<TFieldValues>
|
||||
}
|
||||
|
||||
/**
|
||||
* Partial form to map an internal string field to an external API.
|
||||
*/
|
||||
export const StringMappingForm = <TFieldValues extends FieldValues, TContext extends object>({ register, control, fieldMappingId }: StringMappingFormProps<TFieldValues, TContext>) => {
|
||||
export const StringMappingForm = <TFieldValues extends FieldValues, TContext extends object>({ register, control, fieldMappingId, formState }: StringMappingFormProps<TFieldValues, TContext>) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const { fields, append, remove } = useFieldArray({ control, name: 'auth_provider_mappings_attributes_transformation_mapping' as ArrayPath<TFieldValues> });
|
||||
@ -33,10 +34,12 @@ export const StringMappingForm = <TFieldValues extends FieldValues, TContext ext
|
||||
<FormInput id={`auth_provider_mappings_attributes.${fieldMappingId}.transformation.mapping.${index}.from`}
|
||||
register={register}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
label={t('app.admin.authentication.string_mapping_form.mapping_from')} />
|
||||
<FormInput id={`auth_provider_mappings_attributes.${fieldMappingId}.transformation.mapping.${index}.to`}
|
||||
register={register}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
label={t('app.admin.authentication.string_mapping_form.mapping_to')} />
|
||||
</div>
|
||||
<div className="actions">
|
||||
|
@ -2,7 +2,7 @@ import { FabModal } from '../base/fab-modal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IntegerMappingForm } from './integer-mapping-form';
|
||||
import { UseFormRegister } from 'react-hook-form';
|
||||
import { Control } from 'react-hook-form/dist/types/form';
|
||||
import { Control, FormState } from 'react-hook-form/dist/types/form';
|
||||
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||
import { mappingType } from '../../models/authentication-provider';
|
||||
import { BooleanMappingForm } from './boolean-mapping-form';
|
||||
@ -19,6 +19,7 @@ interface TypeMappingModalProps<TFieldValues, TContext extends object> {
|
||||
register: UseFormRegister<TFieldValues>,
|
||||
control: Control<TFieldValues, TContext>,
|
||||
fieldMappingId: number,
|
||||
formState: FormState<TFieldValues>
|
||||
}
|
||||
|
||||
/**
|
||||
@ -27,7 +28,7 @@ interface TypeMappingModalProps<TFieldValues, TContext extends object> {
|
||||
*
|
||||
* This component is intended to be used in a react-hook-form context.
|
||||
*/
|
||||
export const TypeMappingModal = <TFieldValues extends FieldValues, TContext extends object>({ model, field, type, isOpen, toggleModal, register, control, fieldMappingId }:TypeMappingModalProps<TFieldValues, TContext>) => {
|
||||
export const TypeMappingModal = <TFieldValues extends FieldValues, TContext extends object>({ model, field, type, isOpen, toggleModal, register, control, fieldMappingId, formState }:TypeMappingModalProps<TFieldValues, TContext>) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
return (
|
||||
@ -42,10 +43,10 @@ export const TypeMappingModal = <TFieldValues extends FieldValues, TContext exte
|
||||
id={`auth_provider_mappings_attributes.${fieldMappingId}.transformation.type`}
|
||||
type="hidden"
|
||||
defaultValue={type} />
|
||||
{type === 'integer' && <IntegerMappingForm register={register} control={control} fieldMappingId={fieldMappingId} />}
|
||||
{type === 'boolean' && <BooleanMappingForm register={register} fieldMappingId={fieldMappingId} />}
|
||||
{type === 'date' && <DateMappingForm control={control} fieldMappingId={fieldMappingId} />}
|
||||
{type === 'string' && <StringMappingForm register={register} control={control} fieldMappingId={fieldMappingId} />}
|
||||
{type === 'integer' && <IntegerMappingForm register={register} control={control} fieldMappingId={fieldMappingId} formState={formState} />}
|
||||
{type === 'boolean' && <BooleanMappingForm register={register} fieldMappingId={fieldMappingId} formState={formState} />}
|
||||
{type === 'date' && <DateMappingForm control={control} fieldMappingId={fieldMappingId} formState={formState} />}
|
||||
{type === 'string' && <StringMappingForm register={register} control={control} fieldMappingId={fieldMappingId} formState={formState} />}
|
||||
</FabModal>
|
||||
);
|
||||
};
|
||||
|
@ -33,7 +33,7 @@ const PrepaidPacksPanel: React.FC<PrepaidPacksPanelProps> = ({ user, onError })
|
||||
const [selectedMachine, setSelectedMachine] = useState<Machine>(null);
|
||||
const [packsModal, setPacksModal] = useState<boolean>(false);
|
||||
|
||||
const { handleSubmit, control } = useForm<{ machine_id: number }>();
|
||||
const { handleSubmit, control, formState } = useForm<{ machine_id: number }>();
|
||||
|
||||
useEffect(() => {
|
||||
UserPackAPI.index({ user_id: user.id })
|
||||
@ -125,7 +125,7 @@ const PrepaidPacksPanel: React.FC<PrepaidPacksPanelProps> = ({ user, onError })
|
||||
<div className='prepaid-packs-cta'>
|
||||
<p>{t('app.logged.dashboard.reservations_dashboard.prepaid_packs_panel.cta_info')}</p>
|
||||
<form onSubmit={handleSubmit(onBuyPack)}>
|
||||
<FormSelect options={buildMachinesOptions(machines)} control={control} id="machine_id" rules={{ required: true }} label={t('app.logged.dashboard.reservations_dashboard.prepaid_packs_panel.select_machine')} />
|
||||
<FormSelect options={buildMachinesOptions(machines)} control={control} id="machine_id" rules={{ required: true }} formState={formState} label={t('app.logged.dashboard.reservations_dashboard.prepaid_packs_panel.select_machine')} />
|
||||
<FabButton className='is-black' type="submit">
|
||||
{t('app.logged.dashboard.reservations_dashboard.prepaid_packs_panel.cta_button')}
|
||||
</FabButton>
|
||||
|
@ -186,6 +186,7 @@ export const EventForm: React.FC<EventFormProps> = ({ action, event, onError, on
|
||||
<FormRichText control={control}
|
||||
id="description"
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
label={t('app.admin.event_form.description')}
|
||||
limit={null}
|
||||
heading bulletList blockquote link video image />
|
||||
@ -291,11 +292,13 @@ export const EventForm: React.FC<EventFormProps> = ({ action, event, onError, on
|
||||
control={control}
|
||||
id={`event_price_categories_attributes.${index}.price_category_id`}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
label={t('app.admin.event_form.fare_class')} />
|
||||
<FormInput id={`event_price_categories_attributes.${index}.amount`}
|
||||
register={register}
|
||||
type="number"
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
label={t('app.admin.event_form.price')}
|
||||
addOn={FormatLib.currencySymbol()} />
|
||||
<FabButton className="remove-price is-main" onClick={() => handlePriceRemove(price, index)} icon={<Trash size={20} />} />
|
||||
|
@ -4,7 +4,7 @@ import { AbstractFormComponent } from '../../models/form-component';
|
||||
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||
import { get as _get } from 'lodash';
|
||||
|
||||
export interface AbstractFormItemProps<TFieldValues> extends PropsWithChildren<AbstractFormComponent<TFieldValues>> {
|
||||
export type AbstractFormItemProps<TFieldValues> = PropsWithChildren<AbstractFormComponent<TFieldValues>> & {
|
||||
id: string,
|
||||
label?: string|ReactNode,
|
||||
tooltip?: ReactNode,
|
||||
@ -21,7 +21,7 @@ export interface AbstractFormItemProps<TFieldValues> extends PropsWithChildren<A
|
||||
*/
|
||||
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 [fieldError, setFieldError] = useState<{ message: string }>(error);
|
||||
const [fieldError, setFieldError] = useState<{ message: string }>(null);
|
||||
const [isDisabled, setIsDisabled] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
@ -29,10 +29,6 @@ export const AbstractFormItem = <TFieldValues extends FieldValues>({ id, label,
|
||||
setFieldError(_get(formState?.errors, id));
|
||||
}, [formState]);
|
||||
|
||||
useEffect(() => {
|
||||
setFieldError(error);
|
||||
}, [error]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof disabled === 'function') {
|
||||
setIsDisabled(disabled(id));
|
||||
@ -44,7 +40,7 @@ export const AbstractFormItem = <TFieldValues extends FieldValues>({ id, label,
|
||||
// Compose classnames from props
|
||||
const classNames = [
|
||||
`${className || ''}`,
|
||||
`${isDirty && fieldError ? 'is-incorrect' : ''}`,
|
||||
`${(isDirty && error) || fieldError ? 'is-incorrect' : ''}`,
|
||||
`${isDirty && warning ? 'is-warned' : ''}`,
|
||||
`${rules && rules.required ? 'is-required' : ''}`,
|
||||
`${isDisabled ? 'is-disabled' : ''}`
|
||||
@ -79,7 +75,8 @@ export const AbstractFormItem = <TFieldValues extends FieldValues>({ id, label,
|
||||
</div>}
|
||||
{children}
|
||||
</div>
|
||||
{(isDirty && fieldError) && <div className="form-item-error">{fieldError.message}</div> }
|
||||
{ fieldError && <div className="form-item-error">{fieldError.message}</div> }
|
||||
{(isDirty && error) && <div className="form-item-error">{error.message}</div> }
|
||||
{(isDirty && warning) && <div className="form-item-warning">{warning.message}</div> }
|
||||
</>
|
||||
));
|
||||
|
@ -9,7 +9,7 @@ import { AbstractFormItem, AbstractFormItemProps } from './abstract-form-item';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { ChecklistOption } from '../../models/select';
|
||||
|
||||
interface FormChecklistProps<TFieldValues, TOptionValue, TContext extends object> extends FormControlledComponent<TFieldValues, TContext>, AbstractFormItemProps<TFieldValues> {
|
||||
type FormChecklistProps<TFieldValues, TOptionValue, TContext extends object> = FormControlledComponent<TFieldValues, TContext> & AbstractFormItemProps<TFieldValues> & {
|
||||
defaultValue?: Array<TOptionValue>,
|
||||
options: Array<ChecklistOption<TOptionValue>>,
|
||||
onChange?: (values: Array<TOptionValue>) => void,
|
||||
|
@ -13,7 +13,7 @@ import { FilePdf, Trash } from 'phosphor-react';
|
||||
import { FileType } from '../../models/file';
|
||||
import FileUploadLib from '../../lib/file-upload';
|
||||
|
||||
interface FormFileUploadProps<TFieldValues> extends FormComponent<TFieldValues>, AbstractFormItemProps<TFieldValues> {
|
||||
type FormFileUploadProps<TFieldValues> = FormComponent<TFieldValues> & AbstractFormItemProps<TFieldValues> & {
|
||||
setValue: UseFormSetValue<TFieldValues>,
|
||||
defaultFile?: FileType,
|
||||
accept?: string,
|
||||
|
@ -14,7 +14,7 @@ import { Trash } from 'phosphor-react';
|
||||
import { ImageType } from '../../models/file';
|
||||
import FileUploadLib from '../../lib/file-upload';
|
||||
|
||||
interface FormImageUploadProps<TFieldValues, TContext extends object> extends FormComponent<TFieldValues>, FormControlledComponent<TFieldValues, TContext>, AbstractFormItemProps<TFieldValues> {
|
||||
type FormImageUploadProps<TFieldValues, TContext extends object> = FormComponent<TFieldValues> & FormControlledComponent<TFieldValues, TContext> & AbstractFormItemProps<TFieldValues> & {
|
||||
setValue: UseFormSetValue<TFieldValues>,
|
||||
defaultImage?: ImageType,
|
||||
accept?: string,
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { ReactNode, useCallback, useState } from 'react';
|
||||
import { ReactNode, useCallback, useState, useEffect } from 'react';
|
||||
import * as React from 'react';
|
||||
import { FieldPathValue } from 'react-hook-form';
|
||||
import { FieldPathValue, UseFormGetValues } from 'react-hook-form';
|
||||
import { debounce as _debounce } from 'lodash';
|
||||
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||
import { FieldPath } from 'react-hook-form/dist/types/path';
|
||||
import { FormComponent } from '../../models/form-component';
|
||||
import { AbstractFormItem, AbstractFormItemProps } from './abstract-form-item';
|
||||
|
||||
interface FormInputProps<TFieldValues, TInputType> extends FormComponent<TFieldValues>, AbstractFormItemProps<TFieldValues> {
|
||||
type FormInputProps<TFieldValues, TInputType> = FormComponent<TFieldValues> & AbstractFormItemProps<TFieldValues> & {
|
||||
icon?: ReactNode,
|
||||
addOn?: ReactNode,
|
||||
addOnAction?: (event: React.MouseEvent<HTMLButtonElement>) => void,
|
||||
@ -22,13 +22,14 @@ interface FormInputProps<TFieldValues, TInputType> extends FormComponent<TFieldV
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void,
|
||||
nullable?: boolean,
|
||||
ariaLabel?: string,
|
||||
maxLength?: number
|
||||
maxLength?: number,
|
||||
getValues?: UseFormGetValues<FieldValues>
|
||||
}
|
||||
|
||||
/**
|
||||
* This component is a template for an input component to use within React Hook Form
|
||||
*/
|
||||
export const FormInput = <TFieldValues extends FieldValues, TInputType>({ id, register, label, tooltip, defaultValue, icon, className, rules, disabled, type, addOn, addOnAction, addOnClassName, addOnAriaLabel, placeholder, error, warning, formState, step, onChange, debounce, accept, nullable = false, ariaLabel, maxLength }: FormInputProps<TFieldValues, TInputType>) => {
|
||||
export const FormInput = <TFieldValues extends FieldValues, TInputType>({ id, register, getValues, label, tooltip, defaultValue, icon, className, rules, disabled, type, addOn, addOnAction, addOnClassName, addOnAriaLabel, placeholder, error, warning, formState, step, onChange, debounce, accept, nullable = false, ariaLabel, maxLength }: FormInputProps<TFieldValues, TInputType>) => {
|
||||
const [characterCount, setCharacterCount] = useState(0);
|
||||
|
||||
/**
|
||||
@ -68,6 +69,13 @@ export const FormInput = <TFieldValues extends FieldValues, TInputType>({ id, re
|
||||
}
|
||||
};
|
||||
|
||||
// If maxLength and getValues is provided, uses input ref to initiate the countdown of characters
|
||||
useEffect(() => {
|
||||
if (getValues && maxLength) {
|
||||
setCharacterCount(getValues(id).length);
|
||||
}
|
||||
}, [maxLength, getValues]);
|
||||
|
||||
// Compose classnames from props
|
||||
const classNames = [
|
||||
`${className || ''}`,
|
||||
|
@ -11,7 +11,7 @@ import { FileType } from '../../models/file';
|
||||
import { UnpackNestedValue } from 'react-hook-form/dist/types';
|
||||
import { FieldPathValue } from 'react-hook-form/dist/types/path';
|
||||
|
||||
interface FormMultiFileUploadProps<TFieldValues, TContext extends object> extends FormComponent<TFieldValues>, FormControlledComponent<TFieldValues, TContext>, AbstractFormItemProps<TFieldValues> {
|
||||
type FormMultiFileUploadProps<TFieldValues, TContext extends object> = FormComponent<TFieldValues> & FormControlledComponent<TFieldValues, TContext> & AbstractFormItemProps<TFieldValues> & {
|
||||
setValue: UseFormSetValue<TFieldValues>,
|
||||
addButtonLabel: ReactNode,
|
||||
accept: string
|
||||
|
@ -11,7 +11,7 @@ import { ImageType } from '../../models/file';
|
||||
import { UnpackNestedValue } from 'react-hook-form/dist/types';
|
||||
import { FieldPathValue } from 'react-hook-form/dist/types/path';
|
||||
|
||||
interface FormMultiImageUploadProps<TFieldValues, TContext extends object> extends FormComponent<TFieldValues>, FormControlledComponent<TFieldValues, TContext>, AbstractFormItemProps<TFieldValues> {
|
||||
type FormMultiImageUploadProps<TFieldValues, TContext extends object> = FormComponent<TFieldValues> & FormControlledComponent<TFieldValues, TContext> & AbstractFormItemProps<TFieldValues> & {
|
||||
setValue: UseFormSetValue<TFieldValues>,
|
||||
addButtonLabel: ReactNode
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Controller, FieldPathValue, Path } from 'react-hook-form';
|
||||
import { UnpackNestedValue } from 'react-hook-form/dist/types/form';
|
||||
|
||||
interface CommonProps<TFieldValues, TContext extends object, TOptionValue> extends FormControlledComponent<TFieldValues, TContext>, AbstractFormItemProps<TFieldValues> {
|
||||
type CommonProps<TFieldValues, TContext extends object, TOptionValue> = FormControlledComponent<TFieldValues, TContext> & AbstractFormItemProps<TFieldValues> & {
|
||||
valuesDefault?: Array<TOptionValue>,
|
||||
onChange?: (values: Array<TOptionValue>) => void,
|
||||
placeholder?: string,
|
||||
|
@ -8,7 +8,7 @@ import { Controller, Path } from 'react-hook-form';
|
||||
import { FieldPath } from 'react-hook-form/dist/types/path';
|
||||
import { FieldPathValue, UnpackNestedValue } from 'react-hook-form/dist/types';
|
||||
|
||||
interface FormRichTextProps<TFieldValues, TContext extends object> extends FormControlledComponent<TFieldValues, TContext>, AbstractFormItemProps<TFieldValues> {
|
||||
type FormRichTextProps<TFieldValues, TContext extends object> = FormControlledComponent<TFieldValues, TContext> & AbstractFormItemProps<TFieldValues> & {
|
||||
valueDefault?: string,
|
||||
limit?: number,
|
||||
heading?: boolean,
|
||||
|
@ -9,7 +9,7 @@ import { FormControlledComponent } from '../../models/form-component';
|
||||
import { AbstractFormItem, AbstractFormItemProps } from './abstract-form-item';
|
||||
import { SelectOption } from '../../models/select';
|
||||
|
||||
interface FormSelectProps<TFieldValues, TContext extends object, TOptionValue, TOptionLabel> extends FormControlledComponent<TFieldValues, TContext>, AbstractFormItemProps<TFieldValues> {
|
||||
type FormSelectProps<TFieldValues, TContext extends object, TOptionValue, TOptionLabel> = FormControlledComponent<TFieldValues, TContext> & AbstractFormItemProps<TFieldValues> & {
|
||||
options: Array<SelectOption<TOptionValue, TOptionLabel>>,
|
||||
valueDefault?: TOptionValue,
|
||||
onChange?: (value: TOptionValue) => void,
|
||||
|
@ -5,7 +5,7 @@ import { Controller, Path } from 'react-hook-form';
|
||||
import Switch from 'react-switch';
|
||||
import { AbstractFormItem, AbstractFormItemProps } from './abstract-form-item';
|
||||
|
||||
interface FormSwitchProps<TFieldValues, TContext extends object> extends FormControlledComponent<TFieldValues, TContext>, AbstractFormItemProps<TFieldValues> {
|
||||
type FormSwitchProps<TFieldValues, TContext extends object> = FormControlledComponent<TFieldValues, TContext> & AbstractFormItemProps<TFieldValues> & {
|
||||
defaultValue?: boolean,
|
||||
onChange?: (value: boolean) => void,
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ enableMapSet();
|
||||
export const VatSettingsModal: React.FC<VatSettingsModalProps> = ({ isOpen, toggleModal, onError, onSuccess }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const { handleSubmit, reset, control, register } = useForm<Record<SettingName, SettingValue>>();
|
||||
const { handleSubmit, reset, control, register, formState } = useForm<Record<SettingName, SettingValue>>();
|
||||
const isActive = useWatch({ control, name: 'invoice_VAT-active' });
|
||||
const generalRate = useWatch({ control, name: 'invoice_VAT-rate' });
|
||||
|
||||
@ -108,11 +108,13 @@ export const VatSettingsModal: React.FC<VatSettingsModalProps> = ({ isOpen, togg
|
||||
<FormInput register={register}
|
||||
id="invoice_VAT-name"
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
tooltip={t('app.admin.vat_settings_modal.VAT_name_help')}
|
||||
label={t('app.admin.vat_settings_modal.VAT_name')} />
|
||||
<FormInput register={register}
|
||||
id="invoice_VAT-rate"
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
tooltip={t('app.admin.vat_settings_modal.VAT_rate_help')}
|
||||
type='number'
|
||||
step={0.001}
|
||||
|
@ -127,12 +127,14 @@ export const MachineForm: React.FC<MachineFormProps> = ({ action, machine, onErr
|
||||
<FormRichText control={control}
|
||||
id="description"
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
label={t('app.admin.machine_form.description')}
|
||||
limit={null}
|
||||
heading bulletList blockquote link image video />
|
||||
<FormRichText control={control}
|
||||
id="spec"
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
label={t('app.admin.machine_form.technical_specifications')}
|
||||
limit={null}
|
||||
heading bulletList link />
|
||||
|
@ -11,6 +11,9 @@ import { MachineCard } from './machine-card';
|
||||
import { MachinesFilters } from './machines-filters';
|
||||
import { User } from '../../models/user';
|
||||
import { EditorialBlock } from '../editorial-block/editorial-block';
|
||||
import SettingAPI from '../../api/setting';
|
||||
import SettingLib from '../../lib/setting';
|
||||
import { SettingValue, machineBannerSettings } from '../../models/setting';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -41,6 +44,17 @@ export const MachinesList: React.FC<MachinesListProps> = ({ onError, onSuccess,
|
||||
category: null
|
||||
});
|
||||
|
||||
const [banner, setBanner] = useState<Record<string, SettingValue>>({});
|
||||
|
||||
// fetch Banner text and button from API
|
||||
const fetchBanner = async () => {
|
||||
SettingAPI.query(machineBannerSettings)
|
||||
.then(settings => {
|
||||
setBanner({ ...SettingLib.bulkMapToObject(settings) });
|
||||
})
|
||||
.catch(onError);
|
||||
};
|
||||
|
||||
// retrieve the full list of machines on component mount
|
||||
useEffect(() => {
|
||||
MachineAPI.index()
|
||||
@ -49,6 +63,7 @@ export const MachinesList: React.FC<MachinesListProps> = ({ onError, onSuccess,
|
||||
MachineCategoryAPI.index()
|
||||
.then(data => setMachineCategories(data))
|
||||
.catch(e => onError(e));
|
||||
fetchBanner();
|
||||
}, []);
|
||||
|
||||
// filter the machines shown when the full list was retrieved
|
||||
@ -96,12 +111,11 @@ export const MachinesList: React.FC<MachinesListProps> = ({ onError, onSuccess,
|
||||
|
||||
return (
|
||||
<div className="machines-list">
|
||||
{/* TODO: Condition to display editorial block */}
|
||||
{false &&
|
||||
{banner.machines_banner_active &&
|
||||
<EditorialBlock
|
||||
text={'<h3>Lorem ipsum dolor sit amet</h3><p>Consectetur adipiscing elit. In eget eros sed odio tristique cursus. Quisque pretium tortor vel lorem tempor, eu egestas lorem laoreet. Pellentesque arcu lectus, rutrum eu volutpat nec, luctus eget sapien. Sed ligula tortor, blandit eget purus sit sed.</p>'}
|
||||
cta={'Pif paf pouf'}
|
||||
url={'https://www.plop.io'} />
|
||||
text={banner.machines_banner_text}
|
||||
cta={banner.machines_banner_cta_active && banner.machines_banner_cta_label}
|
||||
url={banner.machines_banner_cta_active && banner.machines_banner_cta_url} />
|
||||
}
|
||||
<MachinesFilters onFilterChangedBy={handleFilterChangedBy} machineCategories={machineCategories}/>
|
||||
<div className="all-machines">
|
||||
|
@ -22,7 +22,7 @@ interface PlanCategoryFormProps {
|
||||
const PlanCategoryForm: React.FC<PlanCategoryFormProps> = ({ action, category, onSuccess, onError }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const { register, control, handleSubmit } = useForm<PlanCategory>({ defaultValues: { ...category } });
|
||||
const { register, control, handleSubmit, formState } = useForm<PlanCategory>({ defaultValues: { ...category } });
|
||||
/**
|
||||
* The action has been confirmed by the user.
|
||||
* Push the created/updated plan-category to the API.
|
||||
@ -48,7 +48,7 @@ const PlanCategoryForm: React.FC<PlanCategoryFormProps> = ({ action, category, o
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<FormInput id='name' register={register} rules={{ required: 'true' }} label={t('app.admin.plan_category_form.name')} />
|
||||
<FormInput id='name' register={register} rules={{ required: 'true' }} formState={formState} label={t('app.admin.plan_category_form.name')} />
|
||||
|
||||
<FormRichText control={control} id="description" label={t('app.admin.plan_category_form.description')} limit={100} />
|
||||
|
||||
|
@ -94,6 +94,7 @@ export const SpaceForm: React.FC<SpaceFormProps> = ({ action, space, onError, on
|
||||
<FormRichText control={control}
|
||||
id="description"
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
label={t('app.admin.space_form.description')}
|
||||
limit={null}
|
||||
heading bulletList blockquote link video image />
|
||||
|
@ -254,7 +254,7 @@ export const ProductForm: React.FC<ProductFormProps> = ({ product, title, onSucc
|
||||
|
||||
<section>
|
||||
<header>
|
||||
<p className="title">{t('app.admin.store.product_form.assigning_machines')}</p>
|
||||
<p className="title" role="heading">{t('app.admin.store.product_form.assigning_machines')}</p>
|
||||
<p className="description">{t('app.admin.store.product_form.assigning_machines_info')}</p>
|
||||
</header>
|
||||
<div className="content">
|
||||
|
@ -45,7 +45,7 @@ export const StoreProduct: React.FC<StoreProductProps> = ({ productSlug, current
|
||||
setProduct(data);
|
||||
const productImage = _.find(data.product_images_attributes, { is_main: true });
|
||||
if (productImage) {
|
||||
setShowImage(productImage.id);
|
||||
setShowImage(productImage.id as number);
|
||||
}
|
||||
setToCartCount(data.quantity_min ? data.quantity_min : 1);
|
||||
setDisplayToggle(descContainer.current.offsetHeight < descContainer.current.scrollHeight);
|
||||
@ -132,7 +132,7 @@ export const StoreProduct: React.FC<StoreProductProps> = ({ productSlug, current
|
||||
<div className='thumbnails'>
|
||||
{product.product_images_attributes.map(i => (
|
||||
<div key={i.id} className={`picture ${i.id === showImage ? 'is-active' : ''}`}>
|
||||
<img alt='' onClick={() => setShowImage(i.id)} src={i.thumb_attachment_url} />
|
||||
<img alt='' onClick={() => setShowImage(i.id as number)} src={i.thumb_attachment_url} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
@ -31,6 +31,7 @@ export const StoreSettings: React.FC<StoreSettingsProps> = ({ onError, onSuccess
|
||||
.then(settings => {
|
||||
const data = SettingLib.bulkMapToObject(settings);
|
||||
reset(data);
|
||||
console.log(data);
|
||||
})
|
||||
.catch(onError);
|
||||
}, []);
|
||||
|
@ -113,6 +113,7 @@ export const TrainingForm: React.FC<TrainingFormProps> = ({ action, training, on
|
||||
<FormRichText control={control}
|
||||
id="description"
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
label={t('app.admin.training_form.description')}
|
||||
limit={null}
|
||||
heading bulletList blockquote link />
|
||||
|
@ -33,7 +33,7 @@ export const ChangePassword = <TFieldValues extends FieldValues>({ register, onE
|
||||
const [isConfirmedPassword, setIsConfirmedPassword] = React.useState<boolean>(false);
|
||||
const [isPrivileged, setIsPrivileged] = React.useState<boolean>(false);
|
||||
|
||||
const { handleSubmit, register: passwordRegister } = useForm<{ password: string }>();
|
||||
const { handleSubmit, register: passwordRegister, formState: passwordFormState } = useForm<{ password: string }>();
|
||||
|
||||
useEffect(() => {
|
||||
MemberAPI.current().then(operator => {
|
||||
@ -106,6 +106,7 @@ export const ChangePassword = <TFieldValues extends FieldValues>({ register, onE
|
||||
type="password"
|
||||
register={passwordRegister}
|
||||
rules={{ required: true }}
|
||||
formState={passwordFormState}
|
||||
label={t('app.shared.change_password.confirm_current')} />
|
||||
<FabButton type="submit">
|
||||
{t('app.shared.change_password.confirm')}
|
||||
|
@ -40,7 +40,7 @@ type selectGroupOption = { value: number, label: string };
|
||||
*/
|
||||
export const ChangeRoleModal: React.FC<ChangeRoleModalProps> = ({ isOpen, toggleModal, user, onSuccess, onError }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
const { control, handleSubmit } = useForm<RoleFormData>({ defaultValues: { role: user.role, groupId: user.group_id } });
|
||||
const { control, handleSubmit, formState } = useForm<RoleFormData>({ defaultValues: { role: user.role, groupId: user.group_id } });
|
||||
|
||||
const [groups, setGroups] = useState<Array<Group>>([]);
|
||||
|
||||
@ -104,14 +104,16 @@ export const ChangeRoleModal: React.FC<ChangeRoleModalProps> = ({ isOpen, toggle
|
||||
control={control}
|
||||
id="role"
|
||||
label={t('app.admin.change_role_modal.new_role')}
|
||||
rules={{ required: true }} />
|
||||
rules={{ required: true }}
|
||||
formState={formState} />
|
||||
<FormSelect options={buildGroupsOptions()}
|
||||
control={control}
|
||||
disabled={!canChangeGroup()}
|
||||
id="groupId"
|
||||
label={t('app.admin.change_role_modal.new_group')}
|
||||
tooltip={t('app.admin.change_role_modal.new_group_help')}
|
||||
rules={{ required: true }} />
|
||||
rules={{ required: true }}
|
||||
formState={formState} />
|
||||
</form>
|
||||
</FabModal>
|
||||
);
|
||||
|
@ -208,6 +208,7 @@ export const UserProfileForm: React.FC<UserProfileFormProps> = ({ action, size,
|
||||
label={t('app.shared.user_profile_form.date_of_birth')}
|
||||
disabled={isDisabled}
|
||||
rules={{ required: true }}
|
||||
formState={formState}
|
||||
type="date" />
|
||||
<FormInput id="profile_attributes.phone"
|
||||
register={register}
|
||||
@ -230,6 +231,7 @@ export const UserProfileForm: React.FC<UserProfileFormProps> = ({ action, size,
|
||||
register={register}
|
||||
disabled={isDisabled}
|
||||
rules={{ required: fieldsSettings.get('address_required') === 'true' }}
|
||||
formState={formState}
|
||||
label={t('app.shared.user_profile_form.address')} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@ import { UseFormRegister, Validate } from 'react-hook-form';
|
||||
import { Control, FormState } from 'react-hook-form/dist/types/form';
|
||||
|
||||
export type ruleTypes = {
|
||||
required?: boolean | string,
|
||||
required?: boolean | string | { value: boolean, message: string },
|
||||
pattern?: RegExp | { value: RegExp, message: string },
|
||||
minLength?: number | { value: number, message: string },
|
||||
maxLength?: number | { value: number, message: string },
|
||||
@ -16,17 +16,21 @@ export type ruleTypes = {
|
||||
* Automatic error handling is done through the `formState` prop.
|
||||
* Even for manual error/warning, the `formState` prop is required, because it is used to determine if the field is dirty.
|
||||
*/
|
||||
export interface AbstractFormComponent<TFieldValues> {
|
||||
interface AbstractFormComponentCommon {
|
||||
error?: { message: string },
|
||||
warning?: { message: string },
|
||||
rules?: ruleTypes,
|
||||
formState?: FormState<TFieldValues>;
|
||||
warning?: { message: string }
|
||||
}
|
||||
|
||||
export interface FormComponent<TFieldValues> extends AbstractFormComponent<TFieldValues> {
|
||||
type AbstractFormComponentRules<TFieldValues> =
|
||||
{ rules: ruleTypes, formState: FormState<TFieldValues> } |
|
||||
{ rules?: never, formState?: FormState<TFieldValues> };
|
||||
|
||||
export type AbstractFormComponent<TFieldValues> = AbstractFormComponentCommon & AbstractFormComponentRules<TFieldValues>;
|
||||
|
||||
export type FormComponent<TFieldValues> = AbstractFormComponent<TFieldValues> & {
|
||||
register: UseFormRegister<TFieldValues>,
|
||||
}
|
||||
|
||||
export interface FormControlledComponent<TFieldValues, TContext extends object> extends AbstractFormComponent<TFieldValues> {
|
||||
export type FormControlledComponent<TFieldValues, TContext extends object> = AbstractFormComponent<TFieldValues> & {
|
||||
control: Control<TFieldValues, TContext>
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user