mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2024-11-29 10:24:20 +01:00
WIP: stripe modal
This commit is contained in:
parent
1e5f7ea1fd
commit
7d0abebc22
@ -1,15 +1,15 @@
|
||||
import apiClient from './api-client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { CustomAsset } from '../models/custom-asset';
|
||||
import { CustomAsset, CustomAssetName } from '../models/custom-asset';
|
||||
import wrapPromise, { IWrapPromise } from '../lib/wrap-promise';
|
||||
|
||||
export default class CustomAssetAPI {
|
||||
async get (name: string): Promise<CustomAsset> {
|
||||
async get (name: CustomAssetName): Promise<CustomAsset> {
|
||||
const res: AxiosResponse = await apiClient.get(`/api/custom_assets/${name}`);
|
||||
return res?.data?.custom_asset;
|
||||
}
|
||||
|
||||
static get (name: string): IWrapPromise<CustomAsset> {
|
||||
static get (name: CustomAssetName): IWrapPromise<CustomAsset> {
|
||||
const api = new CustomAssetAPI();
|
||||
return wrapPromise(api.get(name));
|
||||
}
|
||||
|
@ -1,17 +1,27 @@
|
||||
import apiClient from './api-client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { Setting } from '../models/setting';
|
||||
import { Setting, SettingName } from '../models/setting';
|
||||
import wrapPromise, { IWrapPromise } from '../lib/wrap-promise';
|
||||
|
||||
export default class SettingAPI {
|
||||
async get (name: string): Promise<Setting> {
|
||||
async get (name: SettingName): Promise<Setting> {
|
||||
const res: AxiosResponse = await apiClient.get(`/api/settings/${name}`);
|
||||
return res?.data?.setting;
|
||||
}
|
||||
|
||||
static get (name: string): IWrapPromise<Setting> {
|
||||
async query (names: Array<SettingName>): Promise<Map<SettingName, any>> {
|
||||
const res: AxiosResponse = await apiClient.get(`/api/settings/?names=[${names.join(',')}]`);
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static get (name: SettingName): IWrapPromise<Setting> {
|
||||
const api = new SettingAPI();
|
||||
return wrapPromise(api.get(name));
|
||||
}
|
||||
|
||||
static query(names: Array<SettingName>): IWrapPromise<Map<SettingName, any>> {
|
||||
const api = new SettingAPI();
|
||||
return wrapPromise(api.query(names));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import Modal from 'react-modal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Loader } from './loader';
|
||||
import CustomAssetAPI from '../api/custom-asset';
|
||||
import { CustomAssetName } from '../models/custom-asset';
|
||||
|
||||
Modal.setAppElement('body');
|
||||
|
||||
@ -17,7 +18,7 @@ interface FabModalProps {
|
||||
confirmButton?: ReactNode
|
||||
}
|
||||
|
||||
const blackLogoFile = CustomAssetAPI.get('logo-black-file');
|
||||
const blackLogoFile = CustomAssetAPI.get(CustomAssetName.LogoBlackFile);
|
||||
|
||||
export const FabModal: React.FC<FabModalProps> = ({ title, isOpen, toggleModal, children, confirmButton }) => {
|
||||
const { t } = useTranslation('shared');
|
||||
|
@ -4,11 +4,11 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Elements } from '@stripe/react-stripe-js';
|
||||
import { IApplication } from '../models/application';
|
||||
import SettingAPI from '../api/setting';
|
||||
import { loadStripe } from "@stripe/stripe-js";
|
||||
import SettingAPI from '../api/setting';
|
||||
import { SettingName } from '../models/setting';
|
||||
|
||||
const stripePublicKey = SettingAPI.get('stripe_public_key');
|
||||
const stripePublicKey = SettingAPI.get(SettingName.StripePublicKey);
|
||||
|
||||
export const StripeElements: React.FC = ({ children }) => {
|
||||
const publicKey = stripePublicKey.read();
|
||||
|
@ -1,8 +1,8 @@
|
||||
/**
|
||||
* This component enables the user to type his card data.
|
||||
* This component enables the user to input his card data.
|
||||
*/
|
||||
|
||||
import React, { FormEvent, ReactNode, useState } from 'react';
|
||||
import React, { ChangeEvent, FormEvent, ReactNode, useState } from 'react';
|
||||
import { CardElement, useElements, useStripe } from '@stripe/react-stripe-js';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { Loader } from './loader';
|
||||
@ -15,8 +15,13 @@ import { WalletInfo } from './wallet-info';
|
||||
import { Reservation } from '../models/reservation';
|
||||
import { User } from '../models/user';
|
||||
import { Wallet } from '../models/wallet';
|
||||
import CustomAssetAPI from '../api/custom-asset';
|
||||
import { CustomAssetName } from '../models/custom-asset';
|
||||
import { PaymentSchedule } from '../models/payment-schedule';
|
||||
import { IFablab } from '../models/fablab';
|
||||
|
||||
declare var Application: IApplication;
|
||||
declare var Fablab: IFablab;
|
||||
|
||||
interface StripeModalProps {
|
||||
isOpen: boolean,
|
||||
@ -27,23 +32,30 @@ interface StripeModalProps {
|
||||
wallet: Wallet,
|
||||
price: number,
|
||||
remainingPrice: number,
|
||||
schedule: PaymentSchedule
|
||||
}
|
||||
|
||||
const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuccess, reservation, currentUser, wallet, price, remainingPrice }) => {
|
||||
const cgvFile = CustomAssetAPI.get(CustomAssetName.CgvFile);
|
||||
|
||||
const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuccess, reservation, currentUser, wallet, price, remainingPrice, schedule }) => {
|
||||
|
||||
const stripe = useStripe();
|
||||
const elements = useElements();
|
||||
const { t } = useTranslation('shared');
|
||||
|
||||
const cgv = cgvFile.read();
|
||||
|
||||
const [errors, setErrors] = useState(null);
|
||||
const [submitState, setSubmitState] = useState(false);
|
||||
const [tos, setTos] = useState(false);
|
||||
|
||||
/**
|
||||
* Handle the submission of the form. Depending on the configuration, it will create the payment method on stripe,
|
||||
* Handle the submission of the form. Depending on the configuration, it will create the payment method on Stripe,
|
||||
* or it will process a payment with the inputted card.
|
||||
*/
|
||||
const handleSubmit = async (event: FormEvent): Promise<void> => {
|
||||
event.preventDefault();
|
||||
setSubmitState(true);
|
||||
|
||||
// Stripe.js has not loaded yet
|
||||
if (!stripe || !elements) { return; }
|
||||
@ -54,6 +66,7 @@ const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuc
|
||||
card: cardElement,
|
||||
});
|
||||
|
||||
setSubmitState(false);
|
||||
if (error) {
|
||||
setErrors(error.message);
|
||||
} else {
|
||||
@ -77,7 +90,32 @@ const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuc
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the form submission button. This button will be shown into the modal footer
|
||||
* Check if the Terms of Sales document is set
|
||||
*/
|
||||
const hasCgv = (): boolean => {
|
||||
return cgv != null;
|
||||
}
|
||||
|
||||
const toggleTos = (event: ChangeEvent): void => {
|
||||
setTos(!tos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we are currently creating a payment schedule
|
||||
*/
|
||||
const isPaymentSchedule = (): boolean => {
|
||||
return schedule !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the formatted localized amount for the given price (eg. 20.5 => "20,50 €")
|
||||
*/
|
||||
const formatPrice = (amount: number): string => {
|
||||
return new Intl.NumberFormat(Fablab.intl_locale, {style: 'currency', currency: Fablab.intl_currency}).format(amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the form submission button. This button will be shown into the modal footer.
|
||||
*/
|
||||
const submitButton = (): ReactNode => {
|
||||
return (
|
||||
@ -86,49 +124,64 @@ const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuc
|
||||
disabled={submitState}
|
||||
form="stripe-form"
|
||||
className="validate-btn">
|
||||
{t('app.shared.buttons.confirm')}
|
||||
{t('app.shared.stripe.confirm_payment_of_', { AMOUNT: formatPrice(remainingPrice) })}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the Stripe's card input
|
||||
*/
|
||||
const cardOptions = {
|
||||
style: {
|
||||
base: {
|
||||
fontSize: '16px',
|
||||
color: '#424770',
|
||||
'::placeholder': { color: '#aab7c4' }
|
||||
},
|
||||
invalid: {
|
||||
color: '#9e2146',
|
||||
iconColor: '#9e2146'
|
||||
},
|
||||
},
|
||||
hidePostalCode: true
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="stripe-modal">
|
||||
<FabModal title={t('app.shared.stripe.online_payment')} isOpen={isOpen} toggleModal={toggleModal} confirmButton={submitButton()}>
|
||||
<StripeElements>
|
||||
<form onSubmit={handleSubmit} id="stripe-form">
|
||||
<WalletInfo reservation={reservation} currentUser={currentUser} wallet={wallet} price={price} remainingPrice={remainingPrice} />
|
||||
<CardElement
|
||||
options={{
|
||||
style: {
|
||||
base: {
|
||||
fontSize: '16px',
|
||||
color: '#424770',
|
||||
'::placeholder': { color: '#aab7c4' }
|
||||
},
|
||||
invalid: {
|
||||
color: '#9e2146',
|
||||
iconColor: '#9e2146'
|
||||
},
|
||||
},
|
||||
hidePostalCode: true
|
||||
}}
|
||||
/>
|
||||
<CardElement options={cardOptions} />
|
||||
</form>
|
||||
</StripeElements>
|
||||
{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 />
|
||||
</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="../../../images/powered_by_stripe.png" alt="powered by stripe" />
|
||||
<img src="../../../images/mastercard.png" alt="mastercard" />
|
||||
<img src="../../../images/visa.png" alt="visa" />
|
||||
</div>
|
||||
</FabModal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const StripeModalWrapper: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuccess, reservation, currentUser, wallet, price, remainingPrice }) => {
|
||||
const StripeModalWrapper: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuccess, reservation, currentUser, wallet, price, remainingPrice, schedule }) => {
|
||||
return (
|
||||
<Loader>
|
||||
<StripeModal isOpen={isOpen} toggleModal={toggleModal} afterSuccess={afterSuccess} reservation={reservation} currentUser={currentUser} wallet={wallet} price={price} remainingPrice={remainingPrice} />
|
||||
<StripeModal isOpen={isOpen} toggleModal={toggleModal} afterSuccess={afterSuccess} reservation={reservation} currentUser={currentUser} wallet={wallet} price={price} remainingPrice={remainingPrice} schedule={schedule} />
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
|
||||
Application.Components.component('stripeModal', react2angular(StripeModalWrapper, ['isOpen', 'toggleModal', 'afterSuccess', 'reservation', 'currentUser', 'wallet', 'price', 'remainingPrice']));
|
||||
Application.Components.component('stripeModal', react2angular(StripeModalWrapper, ['isOpen', 'toggleModal', 'afterSuccess', 'reservation', 'currentUser', 'wallet', 'price', 'remainingPrice', 'schedule']));
|
||||
|
@ -799,6 +799,10 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
|
||||
// "valid" Button label
|
||||
$scope.validButtonName = '';
|
||||
|
||||
// stripe modal state
|
||||
// this is used to collect card data when a payment-schedule was selected, and paid with a card
|
||||
$scope.isOpenStripeModal = false;
|
||||
|
||||
/**
|
||||
* Callback to process the local payment, triggered on button click
|
||||
*/
|
||||
@ -837,6 +841,20 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
|
||||
*/
|
||||
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
|
||||
|
||||
/**
|
||||
* Asynchronously updates the status of the stripe modal
|
||||
*/
|
||||
$scope.toggleStripeModal = function () {
|
||||
setTimeout(() => {
|
||||
$scope.isOpenStripeModal = !$scope.isOpenStripeModal;
|
||||
$scope.$apply();
|
||||
}, 50);
|
||||
};
|
||||
|
||||
$scope.afterCreatePaymentMethod = function (a) {
|
||||
console.log('TODO', a);
|
||||
};
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,15 @@
|
||||
export enum CustomAssetName {
|
||||
LogoFile = 'logo-file',
|
||||
LogoBlackFile = 'logo-black-file',
|
||||
CguFile = 'cgu-file',
|
||||
CgvFile = 'cgv-file',
|
||||
ProfileImageFile = 'profile-image-file',
|
||||
FaviconFile = 'favicon-file'
|
||||
}
|
||||
|
||||
export interface CustomAsset {
|
||||
id: number,
|
||||
name: string,
|
||||
name: CustomAssetName,
|
||||
custom_asset_file_attributes: {
|
||||
id: number,
|
||||
attachment: string
|
||||
|
@ -1,7 +1,108 @@
|
||||
import { HistoryValue } from './history-value';
|
||||
|
||||
export enum SettingName {
|
||||
AboutTitle = 'about_title',
|
||||
AboutBody = 'about_body',
|
||||
AboutContacts = 'about_contacts',
|
||||
PrivacyDraft = 'privacy_draft',
|
||||
PrivacyBody = 'privacy_body',
|
||||
PrivacyDpo = 'privacy_dpo',
|
||||
TwitterName = 'twitter_name',
|
||||
HomeBlogpost = 'home_blogpost',
|
||||
MachineExplicationsAlert = 'machine_explications_alert',
|
||||
TrainingExplicationsAlert = 'training_explications_alert',
|
||||
TrainingInformationMessage = 'training_information_message',
|
||||
SubscriptionExplicationsAlert = 'subscription_explications_alert',
|
||||
InvoiceLogo = 'invoice_logo',
|
||||
InvoiceReference = 'invoice_reference',
|
||||
InvoiceCodeActive = 'invoice_code-active',
|
||||
InvoiceCodeValue = 'invoice_code-value',
|
||||
InvoiceOrderNb = 'invoice_order-nb',
|
||||
InvoiceVATActive = 'invoice_VAT-active',
|
||||
InvoiceVATRate = 'invoice_VAT-rate',
|
||||
InvoiceText = 'invoice_text',
|
||||
InvoiceLegals = 'invoice_legals',
|
||||
BookingWindowStart = 'booking_window_start',
|
||||
BookingWindowEnd = 'booking_window_end',
|
||||
BookingSlotDuration = 'booking_slot_duration',
|
||||
BookingMoveEnable = 'booking_move_enable',
|
||||
BookingMoveDelay = 'booking_move_delay',
|
||||
BookingCancelEnable = 'booking_cancel_enable',
|
||||
BookingCancelDelay = 'booking_cancel_delay',
|
||||
MainColor = 'main_color',
|
||||
SecondaryColor = 'secondary_color',
|
||||
FablabName = 'fablab_name',
|
||||
NameGenre = 'name_genre',
|
||||
ReminderEnable = 'reminder_enable',
|
||||
ReminderDelay = 'reminder_delay',
|
||||
EventExplicationsAlert = 'event_explications_alert',
|
||||
SpaceExplicationsAlert = 'space_explications_alert',
|
||||
VisibilityYearly = 'visibility_yearly',
|
||||
VisibilityOthers = 'visibility_others',
|
||||
DisplayNameEnable = 'display_name_enable',
|
||||
MachinesSortBy = 'machines_sort_by',
|
||||
AccountingJournalCode = 'accounting_journal_code',
|
||||
AccountingCardClientCode = 'accounting_card_client_code',
|
||||
AccountingCardClientLabel = 'accounting_card_client_label',
|
||||
AccountingWalletClientCode = 'accounting_wallet_client_code',
|
||||
AccountingWalletClientLabel = 'accounting_wallet_client_label',
|
||||
AccountingOtherClientCode = 'accounting_other_client_code',
|
||||
AccountingOtherClientLabel = 'accounting_other_client_label',
|
||||
AccountingWalletCode = 'accounting_wallet_code',
|
||||
AccountingWalletLabel = 'accounting_wallet_label',
|
||||
AccountingVATCode = 'accounting_VAT_code',
|
||||
AccountingVATLabel = 'accounting_VAT_label',
|
||||
AccountingSubscriptionCode = 'accounting_subscription_code',
|
||||
AccountingSubscriptionLabel = 'accounting_subscription_label',
|
||||
AccountingMachineCode = 'accounting_Machine_code',
|
||||
AccountingMachineLabel = 'accounting_Machine_label',
|
||||
AccountingTrainingCode = 'accounting_Training_code',
|
||||
AccountingTrainingLabel = 'accounting_Training_label',
|
||||
AccountingEventCode = 'accounting_Event_code',
|
||||
AccountingEventLabel = 'accounting_Event_label',
|
||||
AccountingSpaceCode = 'accounting_Space_code',
|
||||
AccountingSpaceLabel = 'accounting_Space_label',
|
||||
HubLastVersion = 'hub_last_version',
|
||||
HubPublicKey = 'hub_public_key',
|
||||
FabAnalytics = 'fab_analytics',
|
||||
LinkName = 'link_name',
|
||||
HomeContent = 'home_content',
|
||||
HomeCss = 'home_css',
|
||||
Origin = 'origin',
|
||||
Uuid = 'uuid',
|
||||
PhoneRequired = 'phone_required',
|
||||
TrackingId = 'tracking_id',
|
||||
BookOverlappingSlots = 'book_overlapping_slots',
|
||||
SlotDuration = 'slot_duration',
|
||||
EventsInCalendar = 'events_in_calendar',
|
||||
SpacesModule = 'spaces_module',
|
||||
PlansModule = 'plans_module',
|
||||
InvoicingModule = 'invoicing_module',
|
||||
FacebookAppId = 'facebook_app_id',
|
||||
TwitterAnalytics = 'twitter_analytics',
|
||||
RecaptchaSiteKey = 'recaptcha_site_key',
|
||||
RecaptchaSecretKey = 'recaptcha_secret_key',
|
||||
FeatureTourDisplay = 'feature_tour_display',
|
||||
EmailFrom = 'email_from',
|
||||
DisqusShortname = 'disqus_shortname',
|
||||
AllowedCadExtensions = 'allowed_cad_extensions',
|
||||
AllowedCadMimeTypes = 'allowed_cad_mime_types',
|
||||
OpenlabAppId = 'openlab_app_id',
|
||||
OpenlabAppSecret = 'openlab_app_secret',
|
||||
OpenlabDefault = 'openlab_default',
|
||||
OnlinePaymentModule = 'online_payment_module',
|
||||
StripePublicKey = 'stripe_public_key',
|
||||
StripeSecretKey = 'stripe_secret_key',
|
||||
StripeCurrency = 'stripe_currency',
|
||||
InvoicePrefix = 'invoice_prefix',
|
||||
ConfirmationRequired = 'confirmation_required',
|
||||
WalletModule = 'wallet_module',
|
||||
StatisticsModule = 'statistics_module',
|
||||
UpcomingEventsShown = 'upcoming_events_shown'
|
||||
}
|
||||
|
||||
export interface Setting {
|
||||
name: string,
|
||||
name: SettingName,
|
||||
value: string,
|
||||
last_update: Date,
|
||||
history: Array<HistoryValue>
|
||||
|
@ -50,4 +50,6 @@
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-info" ng-click="ok()" ng-disabled="attempting" ng-bind-html="validButtonName"></button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||
<!--TODO, continuer l'intégration de la modal -->
|
||||
<stripe-modal is-open="isOpenStripeModal" toggle-modal="toggleStripeModal" after-success="afterCreatePaymentMethod" reservation="reservation" current-user="currentUser" wallet="wallet" price="" />
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user