mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-03-23 14:19:52 +01:00
refactor API services to use only static methods
Also: separate reserve-button component
This commit is contained in:
parent
67ed329dd7
commit
bff5415a1e
@ -1,17 +1,11 @@
|
||||
import apiClient from './clients/api-client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { CustomAsset, CustomAssetName } from '../models/custom-asset';
|
||||
import wrapPromise, { IWrapPromise } from '../lib/wrap-promise';
|
||||
|
||||
export default class CustomAssetAPI {
|
||||
async get (name: CustomAssetName): Promise<CustomAsset> {
|
||||
static async get (name: CustomAssetName): Promise<CustomAsset> {
|
||||
const res: AxiosResponse = await apiClient.get(`/api/custom_assets/${name}`);
|
||||
return res?.data?.custom_asset;
|
||||
}
|
||||
|
||||
static get (name: CustomAssetName): IWrapPromise<CustomAsset> {
|
||||
const api = new CustomAssetAPI();
|
||||
return wrapPromise(api.get(name));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { AxiosResponse } from 'axios';
|
||||
import { EventTheme } from '../models/event-theme';
|
||||
|
||||
export default class EventThemeAPI {
|
||||
async index (): Promise<Array<EventTheme>> {
|
||||
static async index (): Promise<Array<EventTheme>> {
|
||||
const res: AxiosResponse<Array<EventTheme>> = await apiClient.get(`/api/event_themes`);
|
||||
return res?.data;
|
||||
}
|
||||
|
@ -7,5 +7,10 @@ export default class MachineAPI {
|
||||
const res: AxiosResponse<Array<Machine>> = await apiClient.get(`/api/machines`);
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async get (id: number): Promise<Machine> {
|
||||
const res: AxiosResponse<Machine> = await apiClient.get(`/api/machines/${id}`);
|
||||
return res?.data;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,32 +8,32 @@ import {
|
||||
} from '../models/payment-schedule';
|
||||
|
||||
export default class PaymentScheduleAPI {
|
||||
async list (query: PaymentScheduleIndexRequest): Promise<Array<PaymentSchedule>> {
|
||||
static async list (query: PaymentScheduleIndexRequest): Promise<Array<PaymentSchedule>> {
|
||||
const res: AxiosResponse = await apiClient.post(`/api/payment_schedules/list`, query);
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
async index (query: PaymentScheduleIndexRequest): Promise<Array<PaymentSchedule>> {
|
||||
static async index (query: PaymentScheduleIndexRequest): Promise<Array<PaymentSchedule>> {
|
||||
const res: AxiosResponse = await apiClient.get(`/api/payment_schedules?page=${query.query.page}&size=${query.query.size}`);
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
async cashCheck(paymentScheduleItemId: number): Promise<CashCheckResponse> {
|
||||
static async cashCheck(paymentScheduleItemId: number): Promise<CashCheckResponse> {
|
||||
const res: AxiosResponse = await apiClient.post(`/api/payment_schedules/items/${paymentScheduleItemId}/cash_check`);
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
async refreshItem(paymentScheduleItemId: number): Promise<RefreshItemResponse> {
|
||||
static async refreshItem(paymentScheduleItemId: number): Promise<RefreshItemResponse> {
|
||||
const res: AxiosResponse = await apiClient.post(`/api/payment_schedules/items/${paymentScheduleItemId}/refresh_item`);
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
async payItem(paymentScheduleItemId: number): Promise<PayItemResponse> {
|
||||
static async payItem(paymentScheduleItemId: number): Promise<PayItemResponse> {
|
||||
const res: AxiosResponse = await apiClient.post(`/api/payment_schedules/items/${paymentScheduleItemId}/pay_item`);
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
async cancel (paymentScheduleId: number): Promise<CancelScheduleResponse> {
|
||||
static async cancel (paymentScheduleId: number): Promise<CancelScheduleResponse> {
|
||||
const res: AxiosResponse = await apiClient.put(`/api/payment_schedules/${paymentScheduleId}/cancel`);
|
||||
return res?.data;
|
||||
}
|
||||
|
@ -1,15 +1,14 @@
|
||||
import apiClient from './clients/api-client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { Setting, SettingBulkResult, SettingError, SettingName } from '../models/setting';
|
||||
import wrapPromise, { IWrapPromise } from '../lib/wrap-promise';
|
||||
|
||||
export default class SettingAPI {
|
||||
async get (name: SettingName): Promise<Setting> {
|
||||
static async get (name: SettingName): Promise<Setting> {
|
||||
const res: AxiosResponse<{setting: Setting}> = await apiClient.get(`/api/settings/${name}`);
|
||||
return res?.data?.setting;
|
||||
}
|
||||
|
||||
async query (names: Array<SettingName>): Promise<Map<SettingName, any>> {
|
||||
static async query (names: Array<SettingName>): Promise<Map<SettingName, any>> {
|
||||
const params = new URLSearchParams();
|
||||
params.append('names', `['${names.join("','")}']`);
|
||||
|
||||
@ -17,37 +16,22 @@ export default class SettingAPI {
|
||||
return SettingAPI.toSettingsMap(names, res?.data);
|
||||
}
|
||||
|
||||
async update (name: SettingName, value: any): Promise<Setting> {
|
||||
static async update (name: SettingName, value: any): Promise<Setting> {
|
||||
const res: AxiosResponse = await apiClient.patch(`/api/settings/${name}`, { setting: { value } });
|
||||
if (res.status === 304) { return { name, value }; }
|
||||
return res?.data?.setting;
|
||||
}
|
||||
|
||||
async bulkUpdate (settings: Map<SettingName, any>, transactional: boolean = false): Promise<Map<SettingName, SettingBulkResult>> {
|
||||
static async bulkUpdate (settings: Map<SettingName, any>, transactional: boolean = false): Promise<Map<SettingName, SettingBulkResult>> {
|
||||
const res: AxiosResponse = await apiClient.patch(`/api/settings/bulk_update?transactional=${transactional}`, { settings: SettingAPI.toObjectArray(settings) });
|
||||
return SettingAPI.toBulkMap(res?.data?.settings);
|
||||
}
|
||||
|
||||
async isPresent (name: SettingName): Promise<boolean> {
|
||||
static async isPresent (name: SettingName): Promise<boolean> {
|
||||
const res: AxiosResponse = await apiClient.get(`/api/settings/is_present/${name}`);
|
||||
return res?.data?.isPresent;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
static isPresent (name: SettingName): IWrapPromise<boolean> {
|
||||
const api = new SettingAPI();
|
||||
return wrapPromise(api.isPresent(name));
|
||||
}
|
||||
|
||||
private static toSettingsMap(names: Array<SettingName>, data: Object): Map<SettingName, any> {
|
||||
const map = new Map();
|
||||
names.forEach(name => {
|
||||
|
@ -3,7 +3,7 @@ import { AxiosResponse } from 'axios';
|
||||
import { Theme } from '../models/theme';
|
||||
|
||||
export default class ThemeAPI {
|
||||
async index (): Promise<Array<Theme>> {
|
||||
static async index (): Promise<Array<Theme>> {
|
||||
const res: AxiosResponse<Array<Theme>> = await apiClient.get(`/api/themes`);
|
||||
return res?.data;
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import apiClient from './clients/api-client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import wrapPromise, { IWrapPromise } from '../lib/wrap-promise';
|
||||
import { Wallet } from '../models/wallet';
|
||||
|
||||
export default class WalletAPI {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { ReactNode, BaseSyntheticEvent, useEffect } from 'react';
|
||||
import React, { ReactNode, BaseSyntheticEvent, useEffect, useState } from 'react';
|
||||
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';
|
||||
import { CustomAsset, CustomAssetName } from '../../models/custom-asset';
|
||||
import { FabButton } from './fab-button';
|
||||
|
||||
Modal.setAppElement('body');
|
||||
@ -28,24 +28,25 @@ interface FabModalProps {
|
||||
onCreation?: () => void,
|
||||
}
|
||||
|
||||
// initial request to the API
|
||||
const blackLogoFile = CustomAssetAPI.get(CustomAssetName.LogoBlackFile);
|
||||
|
||||
/**
|
||||
* This component is a template for a modal dialog that wraps the application style
|
||||
*/
|
||||
export const FabModal: React.FC<FabModalProps> = ({ title, isOpen, toggleModal, children, confirmButton, className, width = 'sm', closeButton, customFooter, onConfirm, preventConfirm, onCreation }) => {
|
||||
const { t } = useTranslation('shared');
|
||||
|
||||
const [blackLogo, setBlackLogo] = useState<CustomAsset>(null);
|
||||
|
||||
// initial request to the API to get the theme's logo, for back backgrounds
|
||||
useEffect(() => {
|
||||
CustomAssetAPI.get(CustomAssetName.LogoBlackFile).then(data => setBlackLogo(data));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof onCreation === 'function' && isOpen) {
|
||||
onCreation();
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
// the theme's logo, for back backgrounds
|
||||
const blackLogo = blackLogoFile.read();
|
||||
|
||||
/**
|
||||
* Check if the confirm button should be present
|
||||
*/
|
||||
@ -74,9 +75,9 @@ export const FabModal: React.FC<FabModalProps> = ({ title, isOpen, toggleModal,
|
||||
onRequestClose={toggleModal}>
|
||||
<div className="fab-modal-header">
|
||||
<Loader>
|
||||
<img src={blackLogo.custom_asset_file_attributes.attachment_url}
|
||||
alt={blackLogo.custom_asset_file_attributes.attachment}
|
||||
className="modal-logo" />
|
||||
{blackLogo && <img src={blackLogo.custom_asset_file_attributes.attachment_url}
|
||||
alt={blackLogo.custom_asset_file_attributes.attachment}
|
||||
className="modal-logo" />}
|
||||
</Loader>
|
||||
<h1>{ title }</h1>
|
||||
</div>
|
||||
|
@ -30,7 +30,7 @@ const EventThemes: React.FC<EventThemesProps> = ({ event, onChange }) => {
|
||||
const [themes, setThemes] = useState<Array<EventTheme>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
new EventThemeAPI().index().then(data => setThemes(data));
|
||||
EventThemeAPI.index().then(data => setThemes(data));
|
||||
}, []);
|
||||
|
||||
/**
|
||||
|
@ -1,29 +1,35 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import React, { ReactNode, useState } from 'react';
|
||||
import { Machine } from '../../models/machine';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Loader } from '../base/loader';
|
||||
import { ReserveButton } from './reserve-button';
|
||||
import { User } from '../../models/user';
|
||||
|
||||
interface MachineCardProps {
|
||||
user?: User,
|
||||
machine: Machine,
|
||||
onShowMachine: (machine: Machine) => void,
|
||||
onReserveMachine: (machine: Machine) => void,
|
||||
onLoginRequested: () => Promise<User>,
|
||||
onError: (message: string) => void,
|
||||
}
|
||||
|
||||
/**
|
||||
* This component is a box showing the picture of the given machine and two buttons: one to start the reservation process
|
||||
* and another to redirect the user to the machine description page.
|
||||
*/
|
||||
const MachineCardComponent: React.FC<MachineCardProps> = ({ machine, onShowMachine, onReserveMachine }) => {
|
||||
const MachineCardComponent: React.FC<MachineCardProps> = ({ user, machine, onShowMachine, onReserveMachine, onError, onLoginRequested }) => {
|
||||
const { t } = useTranslation('public');
|
||||
|
||||
// shall we display a loader to prevent double-clicking, while the machine details are loading?
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
/**
|
||||
* Callback triggered when the user clicks on the 'reserve' button.
|
||||
* We handle the training verification process here, before redirecting the user to the reservation calendar.
|
||||
* Callback triggered when the user clicks on the 'reserve' button and has passed all the verifications
|
||||
*/
|
||||
const handleReserveMachine = (): void => {
|
||||
onReserveMachine(machine);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback triggered when the user clicks on the 'view' button
|
||||
*/
|
||||
@ -49,16 +55,23 @@ const MachineCardComponent: React.FC<MachineCardProps> = ({ machine, onShowMachi
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="machine-card">
|
||||
<div className={`machine-card ${loading ? 'loading' : ''}`}>
|
||||
{machinePicture()}
|
||||
<div className="machine-name">
|
||||
{machine.name}
|
||||
</div>
|
||||
<div className="machine-actions">
|
||||
{!machine.disabled && <button onClick={handleReserveMachine} className="reserve-button">
|
||||
{!machine.disabled && <ReserveButton currentUser={user}
|
||||
machineId={machine.id}
|
||||
onLoadingStart={() => setLoading(true)}
|
||||
onLoadingEnd={() => setLoading(false)}
|
||||
onError={onError}
|
||||
onReserveMachine={handleReserveMachine}
|
||||
onLoginRequested={onLoginRequested}
|
||||
className="reserve-button">
|
||||
<i className="fas fa-bookmark" />
|
||||
{t('app.public.machine_card.book')}
|
||||
</button>}
|
||||
</ReserveButton>}
|
||||
<button onClick={handleShowMachine} className={`show-button ${machine.disabled ? 'single-button': ''}`}>
|
||||
<i className="fas fa-eye" />
|
||||
{t('app.public.machine_card.consult')}
|
||||
@ -69,10 +82,10 @@ const MachineCardComponent: React.FC<MachineCardProps> = ({ machine, onShowMachi
|
||||
}
|
||||
|
||||
|
||||
export const MachineCard: React.FC<MachineCardProps> = ({ machine, onShowMachine, onReserveMachine }) => {
|
||||
export const MachineCard: React.FC<MachineCardProps> = ({ user, machine, onShowMachine, onReserveMachine, onError, onLoginRequested }) => {
|
||||
return (
|
||||
<Loader>
|
||||
<MachineCardComponent machine={machine} onShowMachine={onShowMachine} onReserveMachine={onReserveMachine} />
|
||||
<MachineCardComponent user={user} machine={machine} onShowMachine={onShowMachine} onReserveMachine={onReserveMachine} onError={onError} onLoginRequested={onLoginRequested} />
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
|
@ -6,19 +6,22 @@ import { Loader } from '../base/loader';
|
||||
import MachineAPI from '../../api/machine';
|
||||
import { MachineCard } from './machine-card';
|
||||
import { MachinesFilters } from './machines-filters';
|
||||
import { User } from '../../models/user';
|
||||
|
||||
declare var Application: IApplication;
|
||||
|
||||
interface MachinesListProps {
|
||||
user?: User,
|
||||
onError: (message: string) => void,
|
||||
onShowMachine: (machine: Machine) => void,
|
||||
onReserveMachine: (machine: Machine) => void,
|
||||
onLoginRequested: () => Promise<User>,
|
||||
}
|
||||
|
||||
/**
|
||||
* This component shows a list of all machines and allows filtering on that list.
|
||||
*/
|
||||
const MachinesList: React.FC<MachinesListProps> = ({ onError, onShowMachine, onReserveMachine }) => {
|
||||
const MachinesList: React.FC<MachinesListProps> = ({ onError, onShowMachine, onReserveMachine, onLoginRequested, user }) => {
|
||||
// shown machines
|
||||
const [machines, setMachines] = useState<Array<Machine>>(null);
|
||||
// we keep the full list of machines, for filtering
|
||||
@ -52,7 +55,13 @@ const MachinesList: React.FC<MachinesListProps> = ({ onError, onShowMachine, onR
|
||||
<MachinesFilters onStatusSelected={handleFilterByStatus} />
|
||||
<div className="all-machines">
|
||||
{machines && machines.map(machine => {
|
||||
return <MachineCard key={machine.id} machine={machine} onShowMachine={onShowMachine} onReserveMachine={onReserveMachine} />
|
||||
return <MachineCard key={machine.id}
|
||||
user={user}
|
||||
machine={machine}
|
||||
onShowMachine={onShowMachine}
|
||||
onReserveMachine={onReserveMachine}
|
||||
onError={onError}
|
||||
onLoginRequested={onLoginRequested} />
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
@ -60,12 +69,12 @@ const MachinesList: React.FC<MachinesListProps> = ({ onError, onShowMachine, onR
|
||||
}
|
||||
|
||||
|
||||
const MachinesListWrapper: React.FC<MachinesListProps> = ({ onError, onShowMachine, onReserveMachine }) => {
|
||||
const MachinesListWrapper: React.FC<MachinesListProps> = ({ user, onError, onShowMachine, onReserveMachine, onLoginRequested }) => {
|
||||
return (
|
||||
<Loader>
|
||||
<MachinesList onError={onError} onShowMachine={onShowMachine} onReserveMachine={onReserveMachine} />
|
||||
<MachinesList user={user} onError={onError} onShowMachine={onShowMachine} onReserveMachine={onReserveMachine} onLoginRequested={onLoginRequested} />
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
|
||||
Application.Components.component('machinesList', react2angular(MachinesListWrapper, ['onError', 'onShowMachine', 'onReserveMachine']));
|
||||
Application.Components.component('machinesList', react2angular(MachinesListWrapper, ['user', 'onError', 'onShowMachine', 'onReserveMachine', 'onLoginRequested']));
|
||||
|
@ -0,0 +1,74 @@
|
||||
import React, { useState } from 'react';
|
||||
import MachineAPI from '../../api/machine';
|
||||
import { Machine } from '../../models/machine';
|
||||
import { User } from '../../models/user';
|
||||
|
||||
interface ReserveButtonProps {
|
||||
currentUser?: User,
|
||||
machineId: number,
|
||||
onLoadingStart?: () => void,
|
||||
onLoadingEnd?: () => void,
|
||||
onError: (message: string) => void,
|
||||
onReserveMachine: (machineId: number) => void,
|
||||
onLoginRequested: () => Promise<User>,
|
||||
className?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Button component that makes the training verification before redirecting the user to the reservation calendar
|
||||
*/
|
||||
export const ReserveButton: React.FC<ReserveButtonProps> = ({ currentUser, machineId, onLoginRequested, onLoadingStart, onLoadingEnd, onError, onReserveMachine, className, children }) => {
|
||||
|
||||
const [pendingTraining, setPendingTraining] = useState<boolean>(false);
|
||||
|
||||
/**
|
||||
* Callback triggered when the user clicks on the 'reserve' button.
|
||||
* We load the full machine data, then we check if the user has passed the training for it (if it's needed)
|
||||
*/
|
||||
const handleClick = (user?: User): void => {
|
||||
if (onLoadingStart) onLoadingStart();
|
||||
|
||||
MachineAPI.get(machineId)
|
||||
.then(data => {
|
||||
checkTraining(data, user);
|
||||
if (onLoadingEnd) onLoadingEnd();
|
||||
})
|
||||
.catch(error => {
|
||||
onError(error);
|
||||
if (onLoadingEnd) onLoadingEnd();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the current user has passed the required training before allowing him to book
|
||||
*/
|
||||
const checkTraining = (machine: Machine, user?: User): void => {
|
||||
// if there's no user currently logged, trigger the logging process
|
||||
if (!user) {
|
||||
onLoginRequested()
|
||||
.then(user => handleClick(user))
|
||||
.catch(error => onError(error));
|
||||
return;
|
||||
}
|
||||
|
||||
// if the currently logged user has completed the training for this machine, or this machine does not require
|
||||
// a prior training, just let him booking
|
||||
if (machine.current_user_is_trained || machine.trainings.length === 0) {
|
||||
return onReserveMachine(machineId);
|
||||
}
|
||||
|
||||
// if a user is authenticated and have booked a training for this machine, tell him that he must wait
|
||||
// for an admin to validate the training before he can book the reservation
|
||||
if (machine.current_user_next_training_reservation) {
|
||||
return setPendingTraining(true);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
<button onClick={() => handleClick(currentUser)} className={className}>
|
||||
{children}
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
}
|
@ -45,8 +45,7 @@ const PaymentSchedulesDashboard: React.FC<PaymentSchedulesDashboardProps> = ({ c
|
||||
const handleLoadMore = (): void => {
|
||||
setPageNumber(pageNumber + 1);
|
||||
|
||||
const api = new PaymentScheduleAPI();
|
||||
api.index({ query: { page: pageNumber + 1, size: PAGE_SIZE }}).then((res) => {
|
||||
PaymentScheduleAPI.index({ query: { page: pageNumber + 1, size: PAGE_SIZE }}).then((res) => {
|
||||
const list = paymentSchedules.concat(res);
|
||||
setPaymentSchedules(list);
|
||||
}).catch((error) => onError(error.message));
|
||||
@ -56,8 +55,7 @@ const PaymentSchedulesDashboard: React.FC<PaymentSchedulesDashboardProps> = ({ c
|
||||
* Reload from te API all the currently displayed payment schedules
|
||||
*/
|
||||
const handleRefreshList = (): void => {
|
||||
const api = new PaymentScheduleAPI();
|
||||
api.index({ query: { page: 1, size: PAGE_SIZE * pageNumber }}).then((res) => {
|
||||
PaymentScheduleAPI.index({ query: { page: 1, size: PAGE_SIZE * pageNumber }}).then((res) => {
|
||||
setPaymentSchedules(res);
|
||||
}).catch((err) => {
|
||||
onError(err.message);
|
||||
|
@ -53,8 +53,7 @@ const PaymentSchedulesList: React.FC<PaymentSchedulesListProps> = ({ currentUser
|
||||
setCustomerFilter(customer);
|
||||
setDateFilter(date);
|
||||
|
||||
const api = new PaymentScheduleAPI();
|
||||
api.list({ query: { reference, customer, date, page: 1, size: PAGE_SIZE }}).then((res) => {
|
||||
PaymentScheduleAPI.list({ query: { reference, customer, date, page: 1, size: PAGE_SIZE }}).then((res) => {
|
||||
setPaymentSchedules(res);
|
||||
}).catch((error) => onError(error.message));
|
||||
};
|
||||
@ -65,8 +64,7 @@ const PaymentSchedulesList: React.FC<PaymentSchedulesListProps> = ({ currentUser
|
||||
const handleLoadMore = (): void => {
|
||||
setPageNumber(pageNumber + 1);
|
||||
|
||||
const api = new PaymentScheduleAPI();
|
||||
api.list({ query: { reference: referenceFilter, customer: customerFilter, date: dateFilter, page: pageNumber + 1, size: PAGE_SIZE }}).then((res) => {
|
||||
PaymentScheduleAPI.list({ query: { reference: referenceFilter, customer: customerFilter, date: dateFilter, page: pageNumber + 1, size: PAGE_SIZE }}).then((res) => {
|
||||
const list = paymentSchedules.concat(res);
|
||||
setPaymentSchedules(list);
|
||||
}).catch((error) => onError(error.message));
|
||||
@ -76,8 +74,7 @@ const PaymentSchedulesList: React.FC<PaymentSchedulesListProps> = ({ currentUser
|
||||
* Reload from te API all the currently displayed payment schedules
|
||||
*/
|
||||
const handleRefreshList = (): void => {
|
||||
const api = new PaymentScheduleAPI();
|
||||
api.list({ query: { reference: referenceFilter, customer: customerFilter, date: dateFilter, page: 1, size: PAGE_SIZE * pageNumber }}).then((res) => {
|
||||
PaymentScheduleAPI.list({ query: { reference: referenceFilter, customer: customerFilter, date: dateFilter, page: 1, size: PAGE_SIZE * pageNumber }}).then((res) => {
|
||||
setPaymentSchedules(res);
|
||||
}).catch((err) => {
|
||||
onError(err.message);
|
||||
|
@ -222,8 +222,7 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
|
||||
* After the user has confirmed that he wants to cash the check, update the API, refresh the list and close the modal.
|
||||
*/
|
||||
const onCheckCashingConfirmed = (): void => {
|
||||
const api = new PaymentScheduleAPI();
|
||||
api.cashCheck(tempDeadline.id).then((res) => {
|
||||
PaymentScheduleAPI.cashCheck(tempDeadline.id).then((res) => {
|
||||
if (res.state === PaymentScheduleItemState.Paid) {
|
||||
refreshSchedulesTable();
|
||||
toggleConfirmCashingModal();
|
||||
@ -267,8 +266,7 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
|
||||
*/
|
||||
const afterAction = (): void => {
|
||||
toggleConfirmActionButton();
|
||||
const api = new PaymentScheduleAPI();
|
||||
api.refreshItem(tempDeadline.id).then(() => {
|
||||
PaymentScheduleAPI.refreshItem(tempDeadline.id).then(() => {
|
||||
refreshSchedulesTable();
|
||||
toggleResolveActionModal();
|
||||
});
|
||||
@ -304,8 +302,7 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
|
||||
*/
|
||||
const handleCardUpdateSuccess = (): void => {
|
||||
if (tempDeadline) {
|
||||
const api = new PaymentScheduleAPI();
|
||||
api.payItem(tempDeadline.id).then(() => {
|
||||
PaymentScheduleAPI.payItem(tempDeadline.id).then(() => {
|
||||
refreshSchedulesTable();
|
||||
onCardUpdateSuccess();
|
||||
toggleUpdateCardModal();
|
||||
@ -347,8 +344,7 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
|
||||
* When the user has confirmed the cancellation, we transfer the request to the API
|
||||
*/
|
||||
const onCancelSubscriptionConfirmed = (): void => {
|
||||
const api = new PaymentScheduleAPI();
|
||||
api.cancel(tempSchedule.id).then(() => {
|
||||
PaymentScheduleAPI.cancel(tempSchedule.id).then(() => {
|
||||
refreshSchedulesTable();
|
||||
toggleCancelSubscriptionModal();
|
||||
});
|
||||
|
@ -4,7 +4,7 @@ import WalletLib from '../../lib/wallet';
|
||||
import { WalletInfo } from '../wallet-info';
|
||||
import { FabModal, ModalSize } from '../base/fab-modal';
|
||||
import { HtmlTranslate } from '../base/html-translate';
|
||||
import { CustomAssetName } from '../../models/custom-asset';
|
||||
import { CustomAsset, CustomAssetName } from '../../models/custom-asset';
|
||||
import { IFablab } from '../../models/fablab';
|
||||
import { ShoppingCart } from '../../models/payment';
|
||||
import { PaymentSchedule } from '../../models/payment-schedule';
|
||||
@ -48,10 +48,6 @@ interface AbstractPaymentModalProps {
|
||||
formClassName?: string,
|
||||
}
|
||||
|
||||
|
||||
// initial request to the API
|
||||
const cgvFile = CustomAssetAPI.get(CustomAssetName.CgvFile);
|
||||
|
||||
/**
|
||||
* This component is an abstract modal that must be extended by each payment gateway to include its payment form.
|
||||
*
|
||||
@ -75,17 +71,18 @@ export const AbstractPaymentModal: React.FC<AbstractPaymentModalProps> = ({ isOp
|
||||
const [tos, setTos] = useState<boolean>(false);
|
||||
// currently active payment gateway
|
||||
const [gateway, setGateway] = useState<string>(null);
|
||||
// the sales conditions
|
||||
const [cgv, setCgv] = useState<CustomAsset>(null);
|
||||
|
||||
const { t } = useTranslation('shared');
|
||||
const cgv = cgvFile.read();
|
||||
|
||||
|
||||
/**
|
||||
* When the component is loaded first, get the name of the currently active payment modal
|
||||
*/
|
||||
useEffect(() => {
|
||||
const api = new SettingAPI();
|
||||
api.get(SettingName.PaymentGateway).then((setting) => {
|
||||
CustomAssetAPI.get(CustomAssetName.CgvFile).then(asset => setCgv(asset));
|
||||
SettingAPI.get(SettingName.PaymentGateway).then((setting) => {
|
||||
// we capitalize the first letter of the name
|
||||
setGateway(setting.value.replace(/^\w/, (c) => c.toUpperCase()));
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { Loader } from '../base/loader';
|
||||
import { StripeModal } from './stripe/stripe-modal';
|
||||
@ -7,7 +7,7 @@ import { IApplication } from '../../models/application';
|
||||
import { ShoppingCart } from '../../models/payment';
|
||||
import { User } from '../../models/user';
|
||||
import { PaymentSchedule } from '../../models/payment-schedule';
|
||||
import { SettingName } from '../../models/setting';
|
||||
import { Setting, SettingName } from '../../models/setting';
|
||||
import { Invoice } from '../../models/invoice';
|
||||
import SettingAPI from '../../api/setting';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -25,16 +25,20 @@ interface PaymentModalProps {
|
||||
customer: User
|
||||
}
|
||||
|
||||
// initial request to the API
|
||||
const paymentGateway = SettingAPI.get(SettingName.PaymentGateway);
|
||||
|
||||
/**
|
||||
* This component open a modal dialog for the configured payment gateway, allowing the user to input his card data
|
||||
* to process an online payment.
|
||||
*/
|
||||
const PaymentModal: React.FC<PaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, currentUser, schedule , cart, customer }) => {
|
||||
const { t } = useTranslation('shared');
|
||||
const gateway = paymentGateway.read();
|
||||
|
||||
const [gateway, setGateway] = useState<Setting>(null);
|
||||
|
||||
useEffect(() => {
|
||||
SettingAPI.get(SettingName.PaymentGateway)
|
||||
.then(setting => setGateway(setting))
|
||||
.catch(error => onError(error));
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Render the Stripe payment modal
|
||||
@ -65,6 +69,8 @@ const PaymentModal: React.FC<PaymentModalProps> = ({ isOpen, toggleModal, afterS
|
||||
/**
|
||||
* Determine which gateway is enabled and return the appropriate payment modal
|
||||
*/
|
||||
if (gateway === null || !isOpen) return <div/>;
|
||||
|
||||
switch (gateway.value) {
|
||||
case 'stripe':
|
||||
return renderStripeModal();
|
||||
|
@ -30,8 +30,7 @@ export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onE
|
||||
const [loadingClass, setLoadingClass] = useState<'hidden' | 'loader' | 'loader-overlay'>('loader');
|
||||
|
||||
useEffect(() => {
|
||||
const api = new SettingAPI();
|
||||
api.query([SettingName.PayZenEndpoint, SettingName.PayZenPublicKey]).then(settings => {
|
||||
SettingAPI.query([SettingName.PayZenEndpoint, SettingName.PayZenPublicKey]).then(settings => {
|
||||
createToken().then(formToken => {
|
||||
// Load the remote library
|
||||
KRGlue.loadLibrary(settings.get(SettingName.PayZenEndpoint), settings.get(SettingName.PayZenPublicKey))
|
||||
|
@ -46,8 +46,7 @@ const PayZenKeysFormComponent: React.FC<PayZenKeysFormProps> = ({ onValidKeys, o
|
||||
* When the component loads for the first time, initialize the keys with the values fetched from the API (if any)
|
||||
*/
|
||||
useEffect(() => {
|
||||
const api = new SettingAPI();
|
||||
api.query(payZenSettings).then(payZenKeys => {
|
||||
SettingAPI.query(payZenSettings).then(payZenKeys => {
|
||||
updateSettings(new Map(payZenKeys));
|
||||
}).catch(error => console.error(error));
|
||||
}, []);
|
||||
|
@ -54,10 +54,9 @@ export const PayzenSettings: React.FC<PayzenSettingsProps> = ({ onEditKeys, onCu
|
||||
* For the private settings, we initialize them with the placeholder value, if the setting is set.
|
||||
*/
|
||||
useEffect(() => {
|
||||
const api = new SettingAPI();
|
||||
api.query(payZenPublicSettings.concat(payZenOtherSettings)).then(payZenKeys => {
|
||||
api.isPresent(SettingName.PayZenPassword).then(pzPassword => {
|
||||
api.isPresent(SettingName.PayZenHmacKey).then(pzHmac => {
|
||||
SettingAPI.query(payZenPublicSettings.concat(payZenOtherSettings)).then(payZenKeys => {
|
||||
SettingAPI.isPresent(SettingName.PayZenPassword).then(pzPassword => {
|
||||
SettingAPI.isPresent(SettingName.PayZenHmacKey).then(pzHmac => {
|
||||
const map = new Map(payZenKeys);
|
||||
map.set(SettingName.PayZenPassword, pzPassword ? PAYZEN_HIDDEN : '');
|
||||
map.set(SettingName.PayZenHmacKey, pzHmac ? PAYZEN_HIDDEN : '');
|
||||
@ -94,8 +93,7 @@ export const PayzenSettings: React.FC<PayzenSettingsProps> = ({ onEditKeys, onCu
|
||||
* This will update the setting on the server.
|
||||
*/
|
||||
const saveCurrency = (): void => {
|
||||
const api = new SettingAPI();
|
||||
api.update(SettingName.PayZenCurrency, settings.get(SettingName.PayZenCurrency)).then(result => {
|
||||
SettingAPI.update(SettingName.PayZenCurrency, settings.get(SettingName.PayZenCurrency)).then(result => {
|
||||
setError('');
|
||||
updateSettings(draft => draft.set(SettingName.PayZenCurrency, result.value));
|
||||
onCurrencyUpdateSuccess(result.value);
|
||||
|
@ -4,9 +4,6 @@ import { loadStripe } from "@stripe/stripe-js";
|
||||
import { SettingName } from '../../../models/setting';
|
||||
import SettingAPI from '../../../api/setting';
|
||||
|
||||
// initial request to the API
|
||||
const stripePublicKey = SettingAPI.get(SettingName.StripePublicKey);
|
||||
|
||||
/**
|
||||
* This component initializes the stripe's Elements tag with the API key
|
||||
*/
|
||||
@ -17,9 +14,10 @@ export const StripeElements: React.FC = memo(({ children }) => {
|
||||
* When this component is mounted, we initialize the <Elements> tag with the Stripe's public key
|
||||
*/
|
||||
useEffect(() => {
|
||||
const key = stripePublicKey.read();
|
||||
const promise = loadStripe(key.value);
|
||||
setStripe(promise);
|
||||
SettingAPI.get(SettingName.StripePublicKey).then(key => {
|
||||
const promise = loadStripe(key.value);
|
||||
setStripe(promise);
|
||||
});
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
@ -43,8 +43,7 @@ const StripeKeysFormComponent: React.FC<StripeKeysFormProps> = ({ onValidKeys, o
|
||||
useEffect(() => {
|
||||
mounted.current = true;
|
||||
|
||||
const api = new SettingAPI();
|
||||
api.query([SettingName.StripePublicKey, SettingName.StripeSecretKey]).then(stripeKeys => {
|
||||
SettingAPI.query([SettingName.StripePublicKey, SettingName.StripeSecretKey]).then(stripeKeys => {
|
||||
setPublicKey(stripeKeys.get(SettingName.StripePublicKey));
|
||||
setSecretKey(stripeKeys.get(SettingName.StripeSecretKey));
|
||||
}).catch(error => console.error(error));
|
||||
|
@ -27,9 +27,6 @@ interface SelectGatewayModalModalProps {
|
||||
onSuccess: (results: Map<SettingName, SettingBulkResult>) => void,
|
||||
}
|
||||
|
||||
// initial request to the API
|
||||
const paymentGateway = SettingAPI.get(SettingName.PaymentGateway);
|
||||
|
||||
const SelectGatewayModal: React.FC<SelectGatewayModalModalProps> = ({ isOpen, toggleModal, onError, onSuccess }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
@ -37,9 +34,11 @@ const SelectGatewayModal: React.FC<SelectGatewayModalModalProps> = ({ isOpen, to
|
||||
const [selectedGateway, setSelectedGateway] = useState<string>('');
|
||||
const [gatewayConfig, setGatewayConfig] = useState<Map<SettingName, string>>(new Map());
|
||||
|
||||
// request the configured gateway to the API
|
||||
useEffect(() => {
|
||||
const gateway = paymentGateway.read();
|
||||
setSelectedGateway(gateway.value ? gateway.value : '');
|
||||
SettingAPI.get(SettingName.PaymentGateway).then(gateway => {
|
||||
setSelectedGateway(gateway.value ? gateway.value : '');
|
||||
})
|
||||
}, []);
|
||||
|
||||
/**
|
||||
@ -101,8 +100,7 @@ const SelectGatewayModal: React.FC<SelectGatewayModalModalProps> = ({ isOpen, to
|
||||
const settings = new Map<SettingName, string>(gatewayConfig);
|
||||
settings.set(SettingName.PaymentGateway, selectedGateway);
|
||||
|
||||
const api = new SettingAPI();
|
||||
api.bulkUpdate(settings, true).then(result => {
|
||||
SettingAPI.bulkUpdate(settings, true).then(result => {
|
||||
const errorResults = Array.from(result.values()).filter(item => !item.status);
|
||||
if (errorResults.length > 0) {
|
||||
onError(errorResults.map(item => item.error[0]).join(' '));
|
||||
|
@ -180,21 +180,38 @@ const _reserveMachine = function (machine, e) {
|
||||
/**
|
||||
* Controller used in the public listing page, allowing everyone to see the list of machines
|
||||
*/
|
||||
Application.Controllers.controller('MachinesController', ['$scope', '$state', '_t', 'AuthService', 'Machine', '$uibModal', 'machinesPromise', 'settingsPromise', 'Member', 'uiTourService',
|
||||
function ($scope, $state, _t, AuthService, Machine, $uibModal, machinesPromise, settingsPromise, Member, uiTourService) {
|
||||
Application.Controllers.controller('MachinesController', ['$scope', '$state', '_t', 'AuthService', 'Machine', '$uibModal', 'machinesPromise', 'settingsPromise', 'Member', 'uiTourService', 'growl',
|
||||
function ($scope, $state, _t, AuthService, Machine, $uibModal, machinesPromise, settingsPromise, Member, uiTourService, growl) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// Retrieve the list of machines
|
||||
$scope.machines = machinesPromise;
|
||||
|
||||
/**
|
||||
* Redirect the user to the machine details page
|
||||
*/
|
||||
* Redirect the user to the machine details page
|
||||
*/
|
||||
$scope.showMachine = function (machine) { $state.go('app.public.machines_show', { id: machine.slug }); };
|
||||
|
||||
/**
|
||||
* Callback to book a reservation for the current machine
|
||||
*/
|
||||
* Shows an error message forwarded from a child component
|
||||
*/
|
||||
$scope.onError = function (message) {
|
||||
growl.error(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the modal dialog to log the user and resolves the returned promise when the logging process
|
||||
* was successfully completed.
|
||||
*/
|
||||
$scope.onLoginRequest = function (e) {
|
||||
return new Promise((resolve, _reject) => {
|
||||
$scope.login(e, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to book a reservation for the current machine
|
||||
*/
|
||||
$scope.reserveMachine = _reserveMachine.bind({
|
||||
$scope,
|
||||
$state,
|
||||
|
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* This function wraps a Promise to make it compatible with react Suspense
|
||||
*/
|
||||
export interface IWrapPromise<T> {
|
||||
read: () => T
|
||||
}
|
||||
|
||||
function wrapPromise(promise: Promise<any>): IWrapPromise<any> {
|
||||
let status: string = 'pending';
|
||||
let response: any;
|
||||
|
||||
const suspender: Promise<any> = promise.then(
|
||||
(res) => {
|
||||
status = 'success'
|
||||
response = res
|
||||
},
|
||||
(err) => {
|
||||
status = 'error'
|
||||
response = err
|
||||
},
|
||||
);
|
||||
|
||||
const read = (): any => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
throw suspender
|
||||
case 'error':
|
||||
throw response
|
||||
default:
|
||||
return response
|
||||
}
|
||||
};
|
||||
|
||||
return { read };
|
||||
}
|
||||
|
||||
export default wrapPromise;
|
@ -5,6 +5,36 @@
|
||||
margin: 0 15px 30px;
|
||||
width: 30%;
|
||||
min-width: 263px;
|
||||
position: relative;
|
||||
|
||||
&.loading::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(29, 29, 29, 0.5);
|
||||
border-radius: 6px;
|
||||
margin: -1px;
|
||||
}
|
||||
|
||||
&.loading::after {
|
||||
content: '\f1ce';
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
text-align: center;
|
||||
font-weight: 900;
|
||||
position: absolute;
|
||||
left: 41%;
|
||||
font-size: 4em;
|
||||
top: 35%;
|
||||
color: white;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
100% { transform: rotate(360deg);}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1219px) {
|
||||
width: 45%;
|
||||
|
@ -41,8 +41,11 @@
|
||||
ui-tour-scroll-parent-id="content-main"
|
||||
post-render="setupMachinesTour">
|
||||
|
||||
<machines-list on-show-machine="showMachine"
|
||||
on-reserve-machine="reserveMachine">
|
||||
<machines-list user="currentUser"
|
||||
on-error="onError"
|
||||
on-show-machine="showMachine"
|
||||
on-reserve-machine="reserveMachine"
|
||||
on-login-requested="onLoginRequest">
|
||||
</machines-list>
|
||||
|
||||
</section>
|
||||
|
Loading…
x
Reference in New Issue
Block a user