mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-27 21:54:27 +01:00
(ui) select authentication provier type
This commit is contained in:
parent
431d733ffe
commit
8fee9c6bb8
@ -6,9 +6,17 @@ import { Loader } from '../base/loader';
|
|||||||
import { IApplication } from '../../models/application';
|
import { IApplication } from '../../models/application';
|
||||||
import { RHFInput } from '../base/rhf-input';
|
import { RHFInput } from '../base/rhf-input';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FabSelect } from '../base/fab-select';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
|
// list of supported authentication methods
|
||||||
|
const METHODS = {
|
||||||
|
DatabaseProvider: 'local_database',
|
||||||
|
OAuth2Provider: 'o_auth2',
|
||||||
|
OpenIdConnectProvider: 'openid_connect'
|
||||||
|
};
|
||||||
|
|
||||||
interface ProviderFormProps {
|
interface ProviderFormProps {
|
||||||
action: 'create' | 'update',
|
action: 'create' | 'update',
|
||||||
provider?: AuthenticationProvider,
|
provider?: AuthenticationProvider,
|
||||||
@ -16,8 +24,10 @@ interface ProviderFormProps {
|
|||||||
onSuccess: (message: string) => void,
|
onSuccess: (message: string) => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type selectProvidableTypeOption = { value: string, label: string };
|
||||||
|
|
||||||
export const ProviderForm: React.FC<ProviderFormProps> = ({ action, provider, onError, onSuccess }) => {
|
export const ProviderForm: React.FC<ProviderFormProps> = ({ action, provider, onError, onSuccess }) => {
|
||||||
const { handleSubmit, register } = useForm<AuthenticationProvider>({ defaultValues: { ...provider } });
|
const { handleSubmit, register, control } = useForm<AuthenticationProvider>({ defaultValues: { ...provider } });
|
||||||
const { t } = useTranslation('shared');
|
const { t } = useTranslation('shared');
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<AuthenticationProvider> = (data: AuthenticationProvider) => {
|
const onSubmit: SubmitHandler<AuthenticationProvider> = (data: AuthenticationProvider) => {
|
||||||
@ -28,9 +38,17 @@ export const ProviderForm: React.FC<ProviderFormProps> = ({ action, provider, on
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buildProvidableTypeOptions = (): Array<selectProvidableTypeOption> => {
|
||||||
|
return Object.keys(METHODS).map((method: string) => {
|
||||||
|
return { value: method, label: t(`app.shared.authentication.${METHODS[method]}`) };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="provider-form" onSubmit={handleSubmit(onSubmit)}>
|
<form className="provider-form" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<RHFInput id="name" register={register} readOnly={action === 'update'} rules={{ required: true }} label={t('app.shared.authentication.name')} />
|
<RHFInput id="name" register={register} readOnly={action === 'update'} rules={{ required: true }} label={t('app.shared.authentication.name')} />
|
||||||
|
<FabSelect id="providable_type" control={control} options={buildProvidableTypeOptions()} valueDefault={'DatabaseProvider'} />
|
||||||
|
<input type={'submit'} />
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
import React, { SelectHTMLAttributes } from 'react';
|
||||||
|
import Select from 'react-select';
|
||||||
|
import { Controller, Path } from 'react-hook-form';
|
||||||
|
import { Control } from 'react-hook-form/dist/types/form';
|
||||||
|
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||||
|
import { FieldPath } from 'react-hook-form/dist/types/path';
|
||||||
|
import { FieldPathValue, UnpackNestedValue } from 'react-hook-form/dist/types';
|
||||||
|
|
||||||
|
interface FabSelectProps<TFieldValues, TContext extends object, TOptionValue> extends SelectHTMLAttributes<HTMLSelectElement> {
|
||||||
|
id: string,
|
||||||
|
label?: string,
|
||||||
|
className?: string,
|
||||||
|
control: Control<TFieldValues, TContext>,
|
||||||
|
placeholder?: string,
|
||||||
|
options: Array<selectOption<TOptionValue>>,
|
||||||
|
valuesDefault?: Array<TOptionValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option format, expected by react-select
|
||||||
|
* @see https://github.com/JedWatson/react-select
|
||||||
|
*/
|
||||||
|
type selectOption<TOptionValue> = { value: TOptionValue, label: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component is a wrapper around react-select to use with react-hook-form.
|
||||||
|
* It is a multi-select component.
|
||||||
|
*/
|
||||||
|
export const FabMultiSelect = <TFieldValues extends FieldValues, TContext extends object, TOptionValue>({ id, label, className, control, placeholder, options, valuesDefault }: FabSelectProps<TFieldValues, TContext, TOptionValue>) => {
|
||||||
|
return (
|
||||||
|
<label className={`fab-multi-select ${className || ''}`}>
|
||||||
|
{label && <div className="fab-multi-select-header">
|
||||||
|
<p>{label}</p>
|
||||||
|
</div>}
|
||||||
|
<div className="fab-multi-select-field">
|
||||||
|
<Controller name={id as FieldPath<TFieldValues>}
|
||||||
|
control={control}
|
||||||
|
defaultValue={valuesDefault as UnpackNestedValue<FieldPathValue<TFieldValues, Path<TFieldValues>>>}
|
||||||
|
render={({ field: { onChange, value, ref } }) =>
|
||||||
|
<Select inputRef={ref}
|
||||||
|
value={options.filter(c => value?.includes(c.value))}
|
||||||
|
onChange={val => onChange(val.map(c => c.value))}
|
||||||
|
placeholder={placeholder}
|
||||||
|
options={options}
|
||||||
|
isMulti />
|
||||||
|
} />
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
};
|
@ -1,20 +1,49 @@
|
|||||||
import React, { SelectHTMLAttributes } from 'react';
|
import React, { SelectHTMLAttributes } from 'react';
|
||||||
import Select from 'react-select';
|
import Select from 'react-select';
|
||||||
import { Controller } from 'react-hook-form';
|
import { Controller, Path } from 'react-hook-form';
|
||||||
|
import { Control } from 'react-hook-form/dist/types/form';
|
||||||
|
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||||
|
import { FieldPath } from 'react-hook-form/dist/types/path';
|
||||||
|
import { FieldPathValue, UnpackNestedValue } from 'react-hook-form/dist/types';
|
||||||
|
|
||||||
interface FabSelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
|
interface FabSelectProps<TFieldValues, TContext extends object, TOptionValue> extends SelectHTMLAttributes<HTMLSelectElement> {
|
||||||
className?: string
|
id: string,
|
||||||
|
label?: string,
|
||||||
|
className?: string,
|
||||||
|
control: Control<TFieldValues, TContext>,
|
||||||
|
placeholder?: string,
|
||||||
|
options: Array<selectOption<TOptionValue>>,
|
||||||
|
valueDefault?: TOptionValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FabSelect: React.FC<FabSelectProps> = ({ className }) => {
|
/**
|
||||||
|
* Option format, expected by react-select
|
||||||
|
* @see https://github.com/JedWatson/react-select
|
||||||
|
*/
|
||||||
|
type selectOption<TOptionValue> = { value: TOptionValue, label: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component is a wrapper for react-select to use with react-hook-form
|
||||||
|
*/
|
||||||
|
export const FabSelect = <TFieldValues extends FieldValues, TContext extends object, TOptionValue>({ id, label, className, control, placeholder, options, valueDefault }: FabSelectProps<TFieldValues, TContext, TOptionValue>) => {
|
||||||
return (
|
return (
|
||||||
<div className={`fab-select ${className || ''}`}>
|
<label className={`fab-select ${className || ''}`}>
|
||||||
<Controller name="description" control={control} render={({ field: { onChange, value } }) =>
|
{label && <div className="fab-select-header">
|
||||||
<Select defaultValue={defaultValues()}
|
<p>{label}</p>
|
||||||
placeholder={t('app.shared.event.select_theme')}
|
</div>}
|
||||||
options={buildOptions()}
|
<div className="fab-select-field">
|
||||||
isMulti />
|
<Controller name={id as FieldPath<TFieldValues>}
|
||||||
} />
|
control={control}
|
||||||
</div>
|
defaultValue={valueDefault as UnpackNestedValue<FieldPathValue<TFieldValues, Path<TFieldValues>>>}
|
||||||
|
render={({ field: { onChange, value, ref } }) =>
|
||||||
|
<Select inputRef={ref}
|
||||||
|
className="fab-select-field-input"
|
||||||
|
value={options.find(c => c.value === value)}
|
||||||
|
onChange={val => onChange(val.value)}
|
||||||
|
placeholder={placeholder}
|
||||||
|
options={options} />
|
||||||
|
} />
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
@import "modules/base/fab-popover";
|
@import "modules/base/fab-popover";
|
||||||
@import "modules/base/labelled-input";
|
@import "modules/base/labelled-input";
|
||||||
@import "modules/base/fab-text-editor";
|
@import "modules/base/fab-text-editor";
|
||||||
|
@import "modules/base/fab-select";
|
||||||
@import "modules/payment-schedule/payment-schedule-summary";
|
@import "modules/payment-schedule/payment-schedule-summary";
|
||||||
@import "modules/wallet-info";
|
@import "modules/wallet-info";
|
||||||
@import "modules/document-filters";
|
@import "modules/document-filters";
|
||||||
@ -81,4 +82,4 @@
|
|||||||
|
|
||||||
@import "app.responsive";
|
@import "app.responsive";
|
||||||
|
|
||||||
@import "overrides"
|
@import "overrides";
|
||||||
|
79
app/frontend/src/stylesheets/modules/base/fab-select.scss
Normal file
79
app/frontend/src/stylesheets/modules/base/fab-select.scss
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
.fab-select,
|
||||||
|
.fab-multi-select {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 1.6rem;
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
p {
|
||||||
|
@include text-sm;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.is-required &-header p::after {
|
||||||
|
content: "*";
|
||||||
|
margin-left: 0.5ch;
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-field {
|
||||||
|
height: 4rem;
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas: "icon input addon";
|
||||||
|
grid-template-columns: min-content 1fr min-content;
|
||||||
|
border: 1px solid var(--gray-soft-dark);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: border-color ease-in-out 0.15s;
|
||||||
|
|
||||||
|
.icon,
|
||||||
|
.addon {
|
||||||
|
width: 4rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--gray-hard-light);
|
||||||
|
background-color: var(--gray-soft);
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
grid-area: icon;
|
||||||
|
border-right: 1px solid var(--gray-soft-dark);
|
||||||
|
}
|
||||||
|
&-input {
|
||||||
|
grid-area: input;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .08);
|
||||||
|
padding: 0 0.8rem;
|
||||||
|
color: var(--gray-hard-darkest);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.addon {
|
||||||
|
grid-area: addon;
|
||||||
|
border-left: 1px solid var(--gray-soft-dark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.is-incorrect &-field {
|
||||||
|
border-color: var(--error);
|
||||||
|
.icon {
|
||||||
|
color: var(--error);
|
||||||
|
border-color: var(--error);
|
||||||
|
background-color: var(--error-lightest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.is-disabled &-field input,
|
||||||
|
&.is-readOnly &-field input {
|
||||||
|
background-color: var(--gray-soft-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-error {
|
||||||
|
margin-top: 0.4rem;
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user