mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-29 18:52:22 +01:00
WIP: fab-input component
This commit is contained in:
parent
5b854ea831
commit
abd6ecabc3
@ -11,7 +11,7 @@ export default class SettingAPI {
|
|||||||
|
|
||||||
async query (names: Array<SettingName>): Promise<Map<SettingName, any>> {
|
async query (names: Array<SettingName>): Promise<Map<SettingName, any>> {
|
||||||
const res: AxiosResponse = await apiClient.get(`/api/settings/?names=[${names.join(',')}]`);
|
const res: AxiosResponse = await apiClient.get(`/api/settings/?names=[${names.join(',')}]`);
|
||||||
return res?.data;
|
return SettingAPI.toSettingsMap(res?.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get (name: SettingName): IWrapPromise<Setting> {
|
static get (name: SettingName): IWrapPromise<Setting> {
|
||||||
@ -23,5 +23,16 @@ export default class SettingAPI {
|
|||||||
const api = new SettingAPI();
|
const api = new SettingAPI();
|
||||||
return wrapPromise(api.query(names));
|
return wrapPromise(api.query(names));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
static toSettingsMap(data: Object): Map<SettingName, any> {
|
||||||
|
const dataArray: Array<Array<string | any>> = Object.entries(data);
|
||||||
|
const map = new Map();
|
||||||
|
dataArray.forEach(item => {
|
||||||
|
map.set(SettingName[item[0]], item[1]);
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
63
app/frontend/src/javascript/components/fab-input.tsx
Normal file
63
app/frontend/src/javascript/components/fab-input.tsx
Normal file
@ -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<FabInputProps> = ({ 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 (
|
||||||
|
<div className={`fab-input ${className ? className : ''}`}>
|
||||||
|
{hasIcon() && <span className="fab-input--icon">{icon}</span>}
|
||||||
|
<input id={id} type={type} className="fab-input--input" value={value} onChange={handleChange} disabled={disabled} required={required} />
|
||||||
|
{hasAddOn() && <span className={`fab-input--addon ${addOnClassName ? addOnClassName : ''}`}>{addOn}</span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FabInput.defaultProps = { type: 'text', debounce: 0 };
|
||||||
|
|
@ -10,6 +10,8 @@ import { IApplication } from '../models/application';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FabModal, ModalSize } from './fab-modal';
|
import { FabModal, ModalSize } from './fab-modal';
|
||||||
import { User } from '../models/user';
|
import { User } from '../models/user';
|
||||||
|
import { Gateway } from '../models/gateway';
|
||||||
|
import { StripeKeysForm } from './stripe-keys-form';
|
||||||
|
|
||||||
|
|
||||||
declare var Application: IApplication;
|
declare var Application: IApplication;
|
||||||
@ -35,12 +37,22 @@ const SelectGatewayModal: React.FC<SelectGatewayModalModalProps> = ({ isOpen, to
|
|||||||
toggleModal();
|
toggleModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the gateway provided by the target input into the component state
|
||||||
|
*/
|
||||||
const setGateway = (event: BaseSyntheticEvent) => {
|
const setGateway = (event: BaseSyntheticEvent) => {
|
||||||
const gateway = event.target.value;
|
const gateway = event.target.value;
|
||||||
setSelectedGateway(gateway);
|
setSelectedGateway(gateway);
|
||||||
setPreventConfirmGateway(!gateway);
|
setPreventConfirmGateway(!gateway);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if any payment gateway was selected
|
||||||
|
*/
|
||||||
|
const hasSelectedGateway = (): boolean => {
|
||||||
|
return selectedGateway !== '';
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FabModal title={t('app.admin.invoices.payment.gateway_modal.select_gateway_title')}
|
<FabModal title={t('app.admin.invoices.payment.gateway_modal.select_gateway_title')}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
@ -51,18 +63,19 @@ const SelectGatewayModal: React.FC<SelectGatewayModalModalProps> = ({ isOpen, to
|
|||||||
confirmButton={t('app.admin.invoices.payment.gateway_modal.confirm_button')}
|
confirmButton={t('app.admin.invoices.payment.gateway_modal.confirm_button')}
|
||||||
onConfirm={onGatewayConfirmed}
|
onConfirm={onGatewayConfirmed}
|
||||||
preventConfirm={preventConfirmGateway}>
|
preventConfirm={preventConfirmGateway}>
|
||||||
<p className="info-gateway">
|
{!hasSelectedGateway() && <p className="info-gateway">
|
||||||
{t('app.admin.invoices.payment.gateway_modal.gateway_info')}
|
{t('app.admin.invoices.payment.gateway_modal.gateway_info')}
|
||||||
</p>
|
</p>}
|
||||||
<label htmlFor="gateway">{t('app.admin.invoices.payment.gateway_modal.select_gateway')}</label>
|
<label htmlFor="gateway">{t('app.admin.invoices.payment.gateway_modal.select_gateway')}</label>
|
||||||
<select id="gateway" className="select-gateway" onChange={setGateway} value={selectedGateway}>
|
<select id="gateway" className="select-gateway" onChange={setGateway} value={selectedGateway}>
|
||||||
<option />
|
<option />
|
||||||
<option value="stripe">{t('app.admin.invoices.payment.gateway_modal.stripe')}</option>
|
<option value={Gateway.Stripe}>{t('app.admin.invoices.payment.gateway_modal.stripe')}</option>
|
||||||
<option value="payzen">{t('app.admin.invoices.payment.gateway_modal.payzen')}</option>
|
<option value={Gateway.PayZen}>{t('app.admin.invoices.payment.gateway_modal.payzen')}</option>
|
||||||
</select>
|
</select>
|
||||||
|
{selectedGateway === Gateway.Stripe && <StripeKeysForm param={'lorem ipsum'} />}
|
||||||
</FabModal>
|
</FabModal>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const SelectGatewayModalWrapper: React.FC<SelectGatewayModalModalProps> = ({ isOpen, toggleModal, currentUser }) => {
|
const SelectGatewayModalWrapper: React.FC<SelectGatewayModalModalProps> = ({ isOpen, toggleModal, currentUser }) => {
|
||||||
return (
|
return (
|
||||||
|
@ -2,11 +2,12 @@
|
|||||||
* Form to set the stripe's public and private keys
|
* 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 { Loader } from './loader';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import SettingAPI from '../api/setting';
|
import SettingAPI from '../api/setting';
|
||||||
import { SettingName } from '../models/setting';
|
import { SettingName } from '../models/setting';
|
||||||
|
import { FabInput } from './fab-input';
|
||||||
|
|
||||||
|
|
||||||
interface StripeKeysFormProps {
|
interface StripeKeysFormProps {
|
||||||
@ -19,7 +20,11 @@ const StripeKeysFormComponent: React.FC<StripeKeysFormProps> = ({ param }) => {
|
|||||||
const { t } = useTranslation('admin');
|
const { t } = useTranslation('admin');
|
||||||
|
|
||||||
const [publicKey, setPublicKey] = useState<string>('');
|
const [publicKey, setPublicKey] = useState<string>('');
|
||||||
|
const [publicKeyAddOn, setPublicKeyAddOn] = useState<ReactNode>(null);
|
||||||
|
const [publicKeyAddOnClassName, setPublicKeyAddOnClassName] = useState<string>('');
|
||||||
const [secretKey, setSecretKey] = useState<string>('');
|
const [secretKey, setSecretKey] = useState<string>('');
|
||||||
|
const [secretKeyAddOn, setSecretKeyAddOn] = useState<ReactNode>(null);
|
||||||
|
const [secretKeyAddOnClassName, setSecretKeyAddOnClassName] = useState<string>('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const keys = stripeKeys.read();
|
const keys = stripeKeys.read();
|
||||||
@ -31,17 +36,27 @@ const StripeKeysFormComponent: React.FC<StripeKeysFormProps> = ({ param }) => {
|
|||||||
// see StripeKeysModalController
|
// see StripeKeysModalController
|
||||||
// from app/frontend/src/javascript/controllers/admin/invoices.js
|
// from app/frontend/src/javascript/controllers/admin/invoices.js
|
||||||
|
|
||||||
|
const testPublicKey = () => {
|
||||||
|
setPublicKeyAddOnClassName('key-valid');
|
||||||
|
setPublicKeyAddOn(<i className="fa fa-check" />);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="stripe-keys-form">
|
||||||
<div className="stripe-keys-info" dangerouslySetInnerHTML={{__html: t('app.admin.invoices.payment.stripe_keys_info_html')}} />
|
<div className="stripe-keys-info" dangerouslySetInnerHTML={{__html: t('app.admin.invoices.payment.stripe_keys_info_html')}} />
|
||||||
<form name="stripeKeysForm">
|
<form name="stripeKeysForm">
|
||||||
<div className="row m-md">
|
<div className="stripe-public-input">
|
||||||
<label htmlFor="stripe_public_key"
|
<label htmlFor="stripe_public_key">{ t('app.admin.invoices.payment.public_key') } *</label>
|
||||||
className="control-label">{ t('app.admin.invoices.payment.public_key') } *</label>
|
<FabInput id="stripe_public_key"
|
||||||
<div className="input-group">
|
icon={<i className="fa fa-info" />}
|
||||||
<span className="input-group-addon"><i className="fa fa-info" /></span>
|
value={publicKey}
|
||||||
|
onChange={testPublicKey}
|
||||||
|
addOn={publicKeyAddOn}
|
||||||
|
addOnClassName={publicKeyAddOnClassName}
|
||||||
|
required />
|
||||||
|
<div className="key-input">
|
||||||
|
<span className="key-input__icon"><i className="fa fa-info" /></span>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
className="form-control"
|
|
||||||
id="stripe_public_key"
|
id="stripe_public_key"
|
||||||
value={publicKey}
|
value={publicKey}
|
||||||
ng-model-options='{ debounce: 200 }'
|
ng-model-options='{ debounce: 200 }'
|
||||||
@ -55,13 +70,11 @@ const StripeKeysFormComponent: React.FC<StripeKeysFormProps> = ({ param }) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="row m-md">
|
<div className="stripe-secret-input">
|
||||||
<label htmlFor="stripe_secret_key"
|
<label htmlFor="stripe_secret_key">{ t('app.admin.invoices.payment.secret_key') } *</label>
|
||||||
className="control-label">{ t('app.admin.invoices.payment.secret_key') } *</label>
|
<div className="key-input">
|
||||||
<div className="input-group">
|
<span className="key-input__icon"><i className="fa fa-key" /></span>
|
||||||
<span className="input-group-addon"><i className="fa fa-key" /></span>
|
|
||||||
<input type="text"
|
<input type="text"
|
||||||
className="form-control"
|
|
||||||
id="stripe_secret_key"
|
id="stripe_secret_key"
|
||||||
value={secretKey}
|
value={secretKey}
|
||||||
ng-model-options='{ debounce: 200 }'
|
ng-model-options='{ debounce: 200 }'
|
||||||
|
5
app/frontend/src/javascript/models/gateway.ts
Normal file
5
app/frontend/src/javascript/models/gateway.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
export enum Gateway {
|
||||||
|
Stripe = 'stripe',
|
||||||
|
PayZen = 'payzen',
|
||||||
|
}
|
@ -22,6 +22,7 @@
|
|||||||
@import "modules/stripe";
|
@import "modules/stripe";
|
||||||
@import "modules/tour";
|
@import "modules/tour";
|
||||||
@import "modules/fab-modal";
|
@import "modules/fab-modal";
|
||||||
|
@import "modules/fab-input";
|
||||||
@import "modules/fab-button";
|
@import "modules/fab-button";
|
||||||
@import "modules/payment-schedule-summary";
|
@import "modules/payment-schedule-summary";
|
||||||
@import "modules/wallet-info";
|
@import "modules/wallet-info";
|
||||||
@ -34,5 +35,6 @@
|
|||||||
@import "modules/payment-schedule-dashboard";
|
@import "modules/payment-schedule-dashboard";
|
||||||
@import "modules/plan-card";
|
@import "modules/plan-card";
|
||||||
@import "modules/select-gateway-modal";
|
@import "modules/select-gateway-modal";
|
||||||
|
@import "modules/stripe-keys-form";
|
||||||
|
|
||||||
@import "app.responsive";
|
@import "app.responsive";
|
||||||
|
63
app/frontend/src/stylesheets/modules/fab-input.scss
Normal file
63
app/frontend/src/stylesheets/modules/fab-input.scss
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
.fab-input {
|
||||||
|
position: relative;
|
||||||
|
display: table;
|
||||||
|
border-collapse: separate;
|
||||||
|
|
||||||
|
&--icon {
|
||||||
|
min-width: 40px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1;
|
||||||
|
color: #555;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #eee;
|
||||||
|
border: 1px solid #c4c4c4;
|
||||||
|
border-radius: 4px 0 0 4px;
|
||||||
|
width: 1%;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: table-cell;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--input {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 38px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #555555;
|
||||||
|
background-color: #fff;
|
||||||
|
background-image: none;
|
||||||
|
border: 1px solid #c4c4c4;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .08);
|
||||||
|
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--addon {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1;
|
||||||
|
color: #555;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #eee;
|
||||||
|
border: 1px solid #c4c4c4;
|
||||||
|
border-radius: 0 4px 4px 0;
|
||||||
|
width: 1%;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: table-cell;
|
||||||
|
|
||||||
|
&:last-child, &:not(:first-child) {
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
22
app/frontend/src/stylesheets/modules/stripe-keys-form.scss
Normal file
22
app/frontend/src/stylesheets/modules/stripe-keys-form.scss
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
.stripe-keys-form {
|
||||||
|
& {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stripe-keys-info {
|
||||||
|
border: 1px solid #bce8f1;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #31708f;
|
||||||
|
background-color: #d9edf7;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stripe-public-input, .stripe-secret-input {
|
||||||
|
display: block;
|
||||||
|
margin: 7px;
|
||||||
|
|
||||||
|
.key-valid {
|
||||||
|
background-color: #7bca38;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user