1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-20 14:54:15 +01:00

style payment modal

This commit is contained in:
Sylvain 2020-11-30 16:52:55 +01:00
parent f5709ef60e
commit 9813c5d27b
7 changed files with 155 additions and 45 deletions

View File

@ -11,16 +11,26 @@ import { CustomAssetName } from '../models/custom-asset';
Modal.setAppElement('body');
export enum ModalSize {
small = 'sm',
medium = 'md',
large = 'lg'
}
interface FabModalProps {
title: string,
isOpen: boolean,
toggleModal: () => void,
confirmButton?: ReactNode
confirmButton?: ReactNode,
closeButton?: boolean,
className?: string,
width?: ModalSize,
customFooter?: ReactNode
}
const blackLogoFile = CustomAssetAPI.get(CustomAssetName.LogoBlackFile);
export const FabModal: React.FC<FabModalProps> = ({ title, isOpen, toggleModal, children, confirmButton }) => {
export const FabModal: React.FC<FabModalProps> = ({ title, isOpen, toggleModal, children, confirmButton, className, width = 'sm', closeButton, customFooter }) => {
const { t } = useTranslation('shared');
const blackLogo = blackLogoFile.read();
@ -31,9 +41,23 @@ export const FabModal: React.FC<FabModalProps> = ({ title, isOpen, toggleModal,
return confirmButton !== undefined;
}
/**
* Should we display the close button?
*/
const hasCloseButton = (): boolean => {
return closeButton;
}
/**
* Check if there's a custom footer
*/
const hasCustomFooter = (): boolean => {
return customFooter !== undefined;
}
return (
<Modal isOpen={isOpen}
className="fab-modal"
className={`fab-modal fab-modal-${width} ${className}`}
overlayClassName="fab-modal-overlay"
onRequestClose={toggleModal}>
<div className="fab-modal-header">
@ -49,8 +73,9 @@ export const FabModal: React.FC<FabModalProps> = ({ title, isOpen, toggleModal,
</div>
<div className="fab-modal-footer">
<Loader>
<button className="modal-btn--close" onClick={toggleModal}>{t('app.shared.buttons.close')}</button>
{hasCloseButton() &&<button className="modal-btn--close" onClick={toggleModal}>{t('app.shared.buttons.close')}</button>}
{hasConfirmButton() && <span className="modal-btn--confirm">{confirmButton}</span>}
{hasCustomFooter() && customFooter}
</Loader>
</div>
</Modal>

View File

@ -6,9 +6,14 @@ interface StripeFormProps {
onSubmit: () => void,
onSuccess: (paymentMethod: PaymentMethod) => void,
onError: (message: string) => void,
className?: string,
}
export const StripeForm: React.FC<StripeFormProps> = ({ onSubmit, onSuccess, onError, children }) => {
/**
* A form component to collect the credit card details and to create the payment method on Stripe.
* The form validation button must be created elsewhere, using the attribute form="stripe-form".
*/
export const StripeForm: React.FC<StripeFormProps> = ({ onSubmit, onSuccess, onError, children, className }) => {
const stripe = useStripe();
const elements = useElements();
@ -56,7 +61,7 @@ export const StripeForm: React.FC<StripeFormProps> = ({ onSubmit, onSuccess, onE
};
return (
<form onSubmit={handleSubmit} id="stripe-form">
<form onSubmit={handleSubmit} id="stripe-form" className={className}>
<CardElement options={cardOptions} />
{children}
</form>

View File

@ -8,7 +8,7 @@ import { Loader } from './loader';
import { IApplication } from '../models/application';
import { StripeElements } from './stripe-elements';
import { useTranslation } from 'react-i18next';
import { FabModal } from './fab-modal';
import { FabModal, ModalSize } from './fab-modal';
import { PaymentMethod } from '@stripe/stripe-js';
import { WalletInfo } from './wallet-info';
import { Reservation } from '../models/reservation';
@ -93,16 +93,16 @@ const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuc
}
/**
* Return the form submission button. This button will be shown into the modal footer.
* Return the logos, shown in the modal footer.
*/
const submitButton = (): ReactNode => {
const logoFooter = (): ReactNode => {
return (
<button type="submit"
disabled={submitState}
form="stripe-form"
className="validate-btn">
{t('app.shared.stripe.confirm_payment_of_', { AMOUNT: formatPrice(remainingPrice) })}
</button>
<div className="stripe-modal-icons">
<i className="fa fa-lock fa-2x m-r-sm pos-rlt" />
<img src={stripeLogo} alt="powered by stripe" />
<img src={mastercardLogo} alt="mastercard" />
<img src={visaLogo} alt="visa" />
</div>
);
}
@ -122,35 +122,40 @@ const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuc
return (
<div className="stripe-modal">
<FabModal title={t('app.shared.stripe.online_payment')} isOpen={isOpen} toggleModal={toggleModal} confirmButton={submitButton()}>
<WalletInfo reservation={reservation} currentUser={currentUser} wallet={wallet} price={price} />
<StripeElements>
<StripeForm onSubmit={handleSubmit} onSuccess={handleFormSuccess} onError={handleFormError}>
{hasCgv() && <div className="terms-of-sales">
<input type="checkbox" id="acceptToS" name="acceptCondition" checked={tos} onChange={toggleTos} required />
<label htmlFor="acceptToS">{ t('app.shared.stripe.i_have_read_and_accept_') }
<a href={cgv.custom_asset_file_attributes.attachment_url} target="_blank">
{ t('app.shared.stripe._the_general_terms_and_conditions') }
</a>
</label>
</div>}
</StripeForm>
</StripeElements>
{hasErrors() && <div className="stripe-errors">
{errors}
</div>}
{isPaymentSchedule() && <div className="payment-schedule-info">
<p>{ t('app.shared.stripe.payment_schedule', { DEADLINES: schedule.items.length }) }</p>
</div>}
<div className="stripe-modal-icons">
<i className="fa fa-lock fa-2x m-r-sm pos-rlt" />
<img src={stripeLogo} alt="powered by stripe" />
<img src={mastercardLogo} alt="mastercard" />
<img src={visaLogo} alt="visa" />
</div>
</FabModal>
</div>
<FabModal title={t('app.shared.stripe.online_payment')}
isOpen={isOpen}
toggleModal={toggleModal}
width={ModalSize.medium}
closeButton={false}
customFooter={logoFooter()}
className="stripe-modal">
<WalletInfo reservation={reservation} currentUser={currentUser} wallet={wallet} price={price} />
<StripeElements>
<StripeForm onSubmit={handleSubmit} onSuccess={handleFormSuccess} onError={handleFormError} className="stripe-form">
{hasErrors() && <div className="stripe-errors">
{errors}
</div>}
{hasCgv() && <div className="terms-of-sales">
<input type="checkbox" id="acceptToS" name="acceptCondition" checked={tos} onChange={toggleTos} required />
<label htmlFor="acceptToS">{ t('app.shared.stripe.i_have_read_and_accept_') }
<a href={cgv.custom_asset_file_attributes.attachment_url} target="_blank">
{ t('app.shared.stripe._the_general_terms_and_conditions') }
</a>
</label>
</div>}
{isPaymentSchedule() && <div className="payment-schedule-info">
<i className="fa fa-warning" />
<p>{ t('app.shared.stripe.payment_schedule', { DEADLINES: schedule.items.length }) }</p>
</div>}
</StripeForm>
<button type="submit"
disabled={submitState}
form="stripe-form"
className="validate-btn">
{t('app.shared.stripe.confirm_payment_of_', { AMOUNT: formatPrice(remainingPrice) })}
</button>
</StripeElements>
</FabModal>
);
}

View File

@ -24,5 +24,6 @@
@import "modules/fab-modal";
@import "modules/payment-schedule-summary";
@import "modules/wallet-info";
@import "modules/stripe-modal";
@import "app.responsive";

View File

@ -19,11 +19,14 @@
animation: 0.15s linear fadeIn;
}
.fab-modal-sm { width: 340px; }
.fab-modal-md { width: 440px; }
.fab-modal-lg { width: 600px; }
.fab-modal {
animation: 0.3s ease-out slideInFromTop;
position: relative;
top: 90px;
width: 340px;
margin: auto;
opacity: 1;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
@ -58,6 +61,7 @@
}
.fab-modal-footer {
position: relative;
padding: 15px;
text-align: right;
border-top: 1px solid #e5e5e5;

View File

@ -0,0 +1,68 @@
.stripe-modal {
.fab-modal-content {
padding-bottom: 0;
}
.stripe-form {
background-color: #f4f3f3;
border: 1px solid #ddd;
border-radius: 6px 6px 0 0;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
padding: 15px;
.stripe-errors {
height: 20px;
padding: 4px 0;
color: #9e2146;
margin-bottom: 1.2em;
}
}
.terms-of-sales {
margin-top: 1em;
margin-bottom: 1em;
font-size: 1.4rem;
font-weight: 600;
input {
display: inline;
margin-right: 0.5em;
}
label {
display: inline;
}
}
.stripe-modal-icons {
text-align: center;
.fa.fa-lock {
top: 7px;
color: #9edd78;
}
img {
margin-right: 10px;
}
}
.payment-schedule-info {
border: 1px solid #faebcc;
border-radius: 4px;
padding: 15px;
background-color: #fcf8e3;
color: #8a6d3b;
.fa.fa-warning {
margin-right: 0.5em;
}
p {
display: inline;
}
}
.validate-btn {
width: 100%;
border: 1px solid #ddd;
border-radius: 0 0 6px 6px;
border-top: 0;
padding: 16px;
color: #fff;
background-color: #1d98ec;
}
}

View File

@ -94,6 +94,7 @@ a.project-author,
a.dsq-brlink,
.alert a,
.about-fablab a,
.terms-of-sales a,
a.collected-infos {
color: $primary;
}
@ -110,6 +111,7 @@ a.dsq-brlink:hover,
.payment-schedule-summary .view-full-schedule:hover,
.alert a:hover,
.about-fablab a:hover,
.terms-of-sales a:hover,
a.collected-infos:hover {
color: $primary-dark;
}