From abd6ecabc3a2cf35e25f7ce3d5619d0d087ee492 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 24 Mar 2021 17:31:50 +0100 Subject: [PATCH] WIP: fab-input component --- app/frontend/src/javascript/api/setting.ts | 13 +++- .../src/javascript/components/fab-input.tsx | 63 +++++++++++++++++++ .../components/select-gateway-modal.tsx | 23 +++++-- .../components/stripe-keys-form.tsx | 41 +++++++----- app/frontend/src/javascript/models/gateway.ts | 5 ++ app/frontend/src/stylesheets/application.scss | 2 + .../src/stylesheets/modules/fab-input.scss | 63 +++++++++++++++++++ .../stylesheets/modules/stripe-keys-form.scss | 22 +++++++ 8 files changed, 212 insertions(+), 20 deletions(-) create mode 100644 app/frontend/src/javascript/components/fab-input.tsx create mode 100644 app/frontend/src/javascript/models/gateway.ts create mode 100644 app/frontend/src/stylesheets/modules/fab-input.scss create mode 100644 app/frontend/src/stylesheets/modules/stripe-keys-form.scss diff --git a/app/frontend/src/javascript/api/setting.ts b/app/frontend/src/javascript/api/setting.ts index 2bea6bf1d..cbc396bfd 100644 --- a/app/frontend/src/javascript/api/setting.ts +++ b/app/frontend/src/javascript/api/setting.ts @@ -11,7 +11,7 @@ export default class SettingAPI { async query (names: Array): Promise> { const res: AxiosResponse = await apiClient.get(`/api/settings/?names=[${names.join(',')}]`); - return res?.data; + return SettingAPI.toSettingsMap(res?.data); } static get (name: SettingName): IWrapPromise { @@ -23,5 +23,16 @@ export default class SettingAPI { const api = new SettingAPI(); return wrapPromise(api.query(names)); } + + private + + static toSettingsMap(data: Object): Map { + const dataArray: Array> = Object.entries(data); + const map = new Map(); + dataArray.forEach(item => { + map.set(SettingName[item[0]], item[1]); + }); + return map; + } } diff --git a/app/frontend/src/javascript/components/fab-input.tsx b/app/frontend/src/javascript/components/fab-input.tsx new file mode 100644 index 000000000..2377f437e --- /dev/null +++ b/app/frontend/src/javascript/components/fab-input.tsx @@ -0,0 +1,63 @@ +/** + * This component is a template for an input component that wraps the application style + */ + +import React, { ReactNode, SyntheticEvent, useCallback } from 'react'; +import { debounce as _debounce } from 'lodash'; + +interface FabInputProps { + id: string, + onChange?: (event: SyntheticEvent) => void, + value: any, + icon?: ReactNode, + addOn?: ReactNode, + addOnClassName?: string, + className?: string, + disabled?: boolean, + required?: boolean, + debounce?: number, + type?: 'text' | 'date' | 'password' | 'url' | 'time' | 'tel' | 'search' | 'number' | 'month' | 'email' | 'datetime-local' | 'week', +} + + +export const FabInput: React.FC = ({ id, onChange, value, icon, className, disabled, type, required, debounce, addOn, addOnClassName }) => { + /** + * Check if the current component was provided an icon to display + */ + const hasIcon = (): boolean => { + return !!icon; + } + + /** + * Check if the current component was provided an add-on element to display, at the end of the input + */ + const hasAddOn = (): boolean => { + return !!addOn; + } + + /** + * Debounced (ie. temporised) version of the 'on change' callback. + * + */ + const handler = useCallback(_debounce(onChange, debounce), []); + + /** + * Handle the action of the button + */ + const handleChange = (e: SyntheticEvent): void => { + if (typeof onChange === 'function') { + handler(e); + } + } + + return ( +
+ {hasIcon() && {icon}} + + {hasAddOn() && {addOn}} +
+ ); +} + +FabInput.defaultProps = { type: 'text', debounce: 0 }; + diff --git a/app/frontend/src/javascript/components/select-gateway-modal.tsx b/app/frontend/src/javascript/components/select-gateway-modal.tsx index 93c71c02a..b3ed13804 100644 --- a/app/frontend/src/javascript/components/select-gateway-modal.tsx +++ b/app/frontend/src/javascript/components/select-gateway-modal.tsx @@ -10,6 +10,8 @@ import { IApplication } from '../models/application'; import { useTranslation } from 'react-i18next'; import { FabModal, ModalSize } from './fab-modal'; import { User } from '../models/user'; +import { Gateway } from '../models/gateway'; +import { StripeKeysForm } from './stripe-keys-form'; declare var Application: IApplication; @@ -35,12 +37,22 @@ const SelectGatewayModal: React.FC = ({ isOpen, to toggleModal(); } + /** + * Save the gateway provided by the target input into the component state + */ const setGateway = (event: BaseSyntheticEvent) => { const gateway = event.target.value; setSelectedGateway(gateway); setPreventConfirmGateway(!gateway); } + /** + * Check if any payment gateway was selected + */ + const hasSelectedGateway = (): boolean => { + return selectedGateway !== ''; + } + return ( = ({ isOpen, to confirmButton={t('app.admin.invoices.payment.gateway_modal.confirm_button')} onConfirm={onGatewayConfirmed} preventConfirm={preventConfirmGateway}> -

+ {!hasSelectedGateway() &&

{t('app.admin.invoices.payment.gateway_modal.gateway_info')} -

+

} + {selectedGateway === Gateway.Stripe && }
); -} +}; const SelectGatewayModalWrapper: React.FC = ({ isOpen, toggleModal, currentUser }) => { return ( diff --git a/app/frontend/src/javascript/components/stripe-keys-form.tsx b/app/frontend/src/javascript/components/stripe-keys-form.tsx index 216170da4..fb1a3de57 100644 --- a/app/frontend/src/javascript/components/stripe-keys-form.tsx +++ b/app/frontend/src/javascript/components/stripe-keys-form.tsx @@ -2,11 +2,12 @@ * Form to set the stripe's public and private keys */ -import React, { useEffect, useState } from 'react'; +import React, { ReactNode, useEffect, useState } from 'react'; import { Loader } from './loader'; import { useTranslation } from 'react-i18next'; import SettingAPI from '../api/setting'; import { SettingName } from '../models/setting'; +import { FabInput } from './fab-input'; interface StripeKeysFormProps { @@ -19,7 +20,11 @@ const StripeKeysFormComponent: React.FC = ({ param }) => { const { t } = useTranslation('admin'); const [publicKey, setPublicKey] = useState(''); + const [publicKeyAddOn, setPublicKeyAddOn] = useState(null); + const [publicKeyAddOnClassName, setPublicKeyAddOnClassName] = useState(''); const [secretKey, setSecretKey] = useState(''); + const [secretKeyAddOn, setSecretKeyAddOn] = useState(null); + const [secretKeyAddOnClassName, setSecretKeyAddOnClassName] = useState(''); useEffect(() => { const keys = stripeKeys.read(); @@ -31,17 +36,27 @@ const StripeKeysFormComponent: React.FC = ({ param }) => { // see StripeKeysModalController // from app/frontend/src/javascript/controllers/admin/invoices.js + const testPublicKey = () => { + setPublicKeyAddOnClassName('key-valid'); + setPublicKeyAddOn(); + } + return ( -
+
-
- -
- +
+ + } + value={publicKey} + onChange={testPublicKey} + addOn={publicKeyAddOn} + addOnClassName={publicKeyAddOnClassName} + required /> +
+ = ({ param }) => {
-
- -
- +
+ +
+