mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-21 15:54:22 +01:00
WIP: create react component to collect card data
This commit is contained in:
parent
0697d9cff1
commit
b88c1009db
17
app/frontend/src/javascript/api/setting.ts
Normal file
17
app/frontend/src/javascript/api/setting.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import apiClient from './api-client';
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
import { Setting } from '../models/setting';
|
||||||
|
import wrapPromise, { IWrapPromise } from '../lib/wrap-promise';
|
||||||
|
|
||||||
|
export default class SettingAPI {
|
||||||
|
async get (name: string): Promise<Setting> {
|
||||||
|
const res: AxiosResponse = await apiClient.get(`/api/settings/${name}`);
|
||||||
|
return res?.data?.setting;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get (name: string): IWrapPromise<Setting> {
|
||||||
|
const api = new SettingAPI();
|
||||||
|
return wrapPromise(api.get(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* This is a compatibility wrapper to allow usage of stripe.js Elements inside of the angular.js app
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { Elements } from '@stripe/react-stripe-js';
|
||||||
|
import { react2angular } from 'react2angular';
|
||||||
|
import { IApplication } from '../../models/application';
|
||||||
|
import SettingAPI from '../../api/setting';
|
||||||
|
import { loadStripe } from "@stripe/stripe-js";
|
||||||
|
|
||||||
|
declare var Application: IApplication;
|
||||||
|
const stripePublicKey = SettingAPI.get('stripe_public_key');
|
||||||
|
|
||||||
|
const ElementsWrapper: React.FC = () => {
|
||||||
|
const publicKey = stripePublicKey.read();
|
||||||
|
const stripePromise = loadStripe(publicKey.value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Elements stripe={stripePromise} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Application.Components.component('stripeElements', react2angular(ElementsWrapper));
|
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
import Switch from 'react-switch';
|
import Switch from 'react-switch';
|
||||||
import { react2angular } from 'react2angular';
|
import { react2angular } from 'react2angular';
|
||||||
import { IApplication } from '../models/application';
|
import { IApplication } from '../../models/application';
|
||||||
|
|
||||||
declare var Application: IApplication;
|
declare var Application: IApplication;
|
||||||
|
|
@ -6,7 +6,7 @@ import React from 'react';
|
|||||||
import Modal from 'react-modal';
|
import Modal from 'react-modal';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Loader } from './loader';
|
import { Loader } from './loader';
|
||||||
import CustomAsset from '../api/custom-asset';
|
import CustomAssetAPI from '../api/custom-asset';
|
||||||
|
|
||||||
Modal.setAppElement('body');
|
Modal.setAppElement('body');
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ interface FabModalProps {
|
|||||||
toggleModal: () => void
|
toggleModal: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const blackLogoFile = CustomAsset.get('logo-black-file');
|
const blackLogoFile = CustomAssetAPI.get('logo-black-file');
|
||||||
|
|
||||||
export const FabModal: React.FC<FabModalProps> = ({ title, isOpen, toggleModal, children }) => {
|
export const FabModal: React.FC<FabModalProps> = ({ title, isOpen, toggleModal, children }) => {
|
||||||
const { t } = useTranslation('shared');
|
const { t } = useTranslation('shared');
|
||||||
|
71
app/frontend/src/javascript/components/stripe-card.tsx
Normal file
71
app/frontend/src/javascript/components/stripe-card.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* This component enables the user to type his card data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { CardElement, useElements, useStripe } from '@stripe/react-stripe-js';
|
||||||
|
import { react2angular } from 'react2angular';
|
||||||
|
import { Loader } from './loader';
|
||||||
|
import { IApplication } from '../models/application';
|
||||||
|
|
||||||
|
|
||||||
|
declare var Application: IApplication;
|
||||||
|
|
||||||
|
const StripeCard: React.FC = () => {
|
||||||
|
|
||||||
|
const stripe = useStripe();
|
||||||
|
const elements = useElements();
|
||||||
|
|
||||||
|
const handleSubmit = async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// Stripe.js has not loaded yet
|
||||||
|
if (!stripe || !elements) { return; }
|
||||||
|
|
||||||
|
const cardElement = elements.getElement(CardElement);
|
||||||
|
|
||||||
|
const { error, paymentMethod } = await stripe.createPaymentMethod({
|
||||||
|
type: 'card',
|
||||||
|
card: cardElement,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.log('[error]', error);
|
||||||
|
} else {
|
||||||
|
console.log('[PaymentMethod]', paymentMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="stripe-card">
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<CardElement
|
||||||
|
options={{
|
||||||
|
style: {
|
||||||
|
base: {
|
||||||
|
fontSize: '16px',
|
||||||
|
color: '#424770',
|
||||||
|
'::placeholder': { color: '#aab7c4' }
|
||||||
|
},
|
||||||
|
invalid: {
|
||||||
|
color: '#9e2146',
|
||||||
|
iconColor: '#9e2146'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const StripeCardWrapper: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Loader>
|
||||||
|
<StripeCard />
|
||||||
|
</Loader>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Application.Components.component('stripeCard', react2angular(StripeCardWrapper));
|
@ -5,7 +5,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { react2angular } from 'react2angular';
|
import { react2angular } from 'react2angular';
|
||||||
import moment from 'moment';
|
|
||||||
import { IApplication } from '../models/application';
|
import { IApplication } from '../models/application';
|
||||||
import '../lib/i18n';
|
import '../lib/i18n';
|
||||||
import { IFilterService } from 'angular';
|
import { IFilterService } from 'angular';
|
||||||
|
@ -705,7 +705,10 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
|
|||||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon', 'cartItems', 'stripeKey', 'schedule',
|
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon', 'cartItems', 'stripeKey', 'schedule',
|
||||||
function ($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, wallet, helpers, $filter, coupon, cartItems, stripeKey, schedule) {
|
function ($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, wallet, helpers, $filter, coupon, cartItems, stripeKey, schedule) {
|
||||||
// user wallet amount
|
// user wallet amount
|
||||||
$scope.walletAmount = wallet.amount;
|
$scope.wallet = wallet;
|
||||||
|
|
||||||
|
// Global price (total of all items)
|
||||||
|
$scope.price = price.price;
|
||||||
|
|
||||||
// Price
|
// Price
|
||||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount);
|
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount);
|
||||||
@ -800,6 +803,9 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
|
|||||||
* Callback to process the local payment, triggered on button click
|
* Callback to process the local payment, triggered on button click
|
||||||
*/
|
*/
|
||||||
$scope.ok = function () {
|
$scope.ok = function () {
|
||||||
|
if ($scope.method.payment_method === 'stripe') {
|
||||||
|
return payByStripe(reservation);
|
||||||
|
}
|
||||||
$scope.attempting = true;
|
$scope.attempting = true;
|
||||||
// save subscription (if there's only a subscription selected)
|
// save subscription (if there's only a subscription selected)
|
||||||
if ($scope.reservation.slots_attributes.length === 0 && selectedPlan) {
|
if ($scope.reservation.slots_attributes.length === 0 && selectedPlan) {
|
||||||
|
9
app/frontend/src/javascript/models/history-value.ts
Normal file
9
app/frontend/src/javascript/models/history-value.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export interface HistoryValue {
|
||||||
|
id: number,
|
||||||
|
value: string,
|
||||||
|
created_at: Date
|
||||||
|
user: {
|
||||||
|
id: number,
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
}
|
8
app/frontend/src/javascript/models/setting.ts
Normal file
8
app/frontend/src/javascript/models/setting.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { HistoryValue } from './history-value';
|
||||||
|
|
||||||
|
export interface Setting {
|
||||||
|
name: string,
|
||||||
|
value: string,
|
||||||
|
last_update: Date,
|
||||||
|
history: Array<HistoryValue>
|
||||||
|
}
|
@ -10,9 +10,13 @@
|
|||||||
<form name="stripeForm" stripe:form cart-items="cartItems" on-payment-success="onPaymentSuccess" stripe-key="{{stripeKey}}" class="form-horizontal">
|
<form name="stripeForm" stripe:form cart-items="cartItems" on-payment-success="onPaymentSuccess" stripe-key="{{stripeKey}}" class="form-horizontal">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
|
|
||||||
<h3 class="m-t-xs" ng-if="walletAmount" ng-bind-html="'app.shared.wallet.you_have_amount_in_wallet' | translate:{ amount: numberFilter(walletAmount, 2), currency: currencySymbol }"></h3>
|
<div class="row">
|
||||||
<p ng-if="walletAmount > 0 && amount !== 0" class="text-italic" ng-bind-html="'app.shared.stripe.credit_amount_for_pay_reservation' | translate:{ amount: numberFilter(amount, 2), currency: currencySymbol }"></p>
|
<wallet-info current-user="currentUser"
|
||||||
|
reservation="reservation"
|
||||||
|
price="price"
|
||||||
|
remaining-price="amount"
|
||||||
|
wallet="wallet"/>
|
||||||
|
</div>
|
||||||
<div id="card-element"></div>
|
<div id="card-element"></div>
|
||||||
<div id="card-errors" role="alert"></div>
|
<div id="card-errors" role="alert"></div>
|
||||||
|
|
||||||
|
@ -88,29 +88,31 @@
|
|||||||
|
|
||||||
<%= flash_messages %>
|
<%= flash_messages %>
|
||||||
|
|
||||||
<section class="vbox">
|
<stripe-elements>
|
||||||
|
<section class="vbox">
|
||||||
|
|
||||||
<header class="header header-md navbar navbar-fixed-top-xs">
|
<header class="header header-md navbar navbar-fixed-top-xs">
|
||||||
<div ui-view="header"></div>
|
<div ui-view="header"></div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section ui-view="content">
|
<section ui-view="content">
|
||||||
<section class="hbox stretch">
|
<section class="hbox stretch">
|
||||||
<aside id="nav" class="aside-md bg-red hidden-print" ui-view="leftnav"></aside>
|
<aside id="nav" class="aside-md bg-red hidden-print" ui-view="leftnav"></aside>
|
||||||
|
|
||||||
<section id="content">
|
<section id="content">
|
||||||
<section class="vbox">
|
<section class="vbox">
|
||||||
<section id="cookies-modal" ui-view="cookies">
|
<section id="cookies-modal" ui-view="cookies">
|
||||||
</section>
|
</section>
|
||||||
<section id="content-main" class="scrollable" ui-view="main">
|
<section id="content-main" class="scrollable" ui-view="main">
|
||||||
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
|
||||||
|
|
||||||
</section> <!-- /.hbox -->
|
</section> <!-- /.hbox -->
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</section> <!-- /.vbox -->
|
</section> <!-- /.vbox -->
|
||||||
|
</stripe-elements>
|
||||||
|
|
||||||
<div class="app-generator">
|
<div class="app-generator">
|
||||||
<span class="app-version" uib-tooltip="{{'app.public.common.version' | translate}} {{version.current}}" ng-if="currentUser && currentUser.role == 'admin'" ng-click="versionModal()">
|
<span class="app-version" uib-tooltip="{{'app.public.common.version' | translate}} {{version.current}}" ng-if="currentUser && currentUser.role == 'admin'" ng-click="versionModal()">
|
||||||
|
@ -41,6 +41,8 @@
|
|||||||
"@claviska/jquery-minicolors": "^2.3.5",
|
"@claviska/jquery-minicolors": "^2.3.5",
|
||||||
"@fortawesome/fontawesome-free": "5.14.0",
|
"@fortawesome/fontawesome-free": "5.14.0",
|
||||||
"@rails/webpacker": "5.2.1",
|
"@rails/webpacker": "5.2.1",
|
||||||
|
"@stripe/react-stripe-js": "^1.1.2",
|
||||||
|
"@stripe/stripe-js": "^1.11.0",
|
||||||
"@types/react": "^16.9.53",
|
"@types/react": "^16.9.53",
|
||||||
"@types/react-dom": "^16.9.8",
|
"@types/react-dom": "^16.9.8",
|
||||||
"@uirouter/angularjs": "0.4",
|
"@uirouter/angularjs": "0.4",
|
||||||
|
12
yarn.lock
12
yarn.lock
@ -1121,6 +1121,18 @@
|
|||||||
webpack-cli "^3.3.12"
|
webpack-cli "^3.3.12"
|
||||||
webpack-sources "^1.4.3"
|
webpack-sources "^1.4.3"
|
||||||
|
|
||||||
|
"@stripe/react-stripe-js@^1.1.2":
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@stripe/react-stripe-js/-/react-stripe-js-1.1.2.tgz#a7f5ef5b4d7dc7fa723501b706644414cfe6dcba"
|
||||||
|
integrity sha512-07hu8RJXwWKGbvdvd1yt1cYvGtDB8jFX+q10f7FQuItUt9rlSo0am3WIx845iMHANiYgxyRb1PS201Yle9xxPQ==
|
||||||
|
dependencies:
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
|
"@stripe/stripe-js@^1.11.0":
|
||||||
|
version "1.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.11.0.tgz#00e812d72a7760dae08237875066d263671478ee"
|
||||||
|
integrity sha512-SDNZKuETBEVkernd1tq8tL6wNfVKrl24Txs3p+4NYxoaIbNaEO7mrln/2Y/WRcQBWjagvhDIM5I6+X1rfK0qhQ==
|
||||||
|
|
||||||
"@types/angular@^1.6.39":
|
"@types/angular@^1.6.39":
|
||||||
version "1.7.3"
|
version "1.7.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.7.3.tgz#138c2f2f688e9dcb413c6052d9483d773ce7f627"
|
resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.7.3.tgz#138c2f2f688e9dcb413c6052d9483d773ce7f627"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user