From 6cd91d0291afead4cbe7e574c9474e1050f0d137 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 12 Oct 2021 17:29:35 +0200 Subject: [PATCH] [WIP] refactor renew subscription modal --- .../payment-schedule-summary.tsx | 2 +- .../payment-schedule/select-schedule.tsx | 6 +- .../components/subscriptions/renew-modal.tsx | 127 ++++++++++++++++++ .../javascript/controllers/admin/members.js | 15 ++- app/frontend/src/stylesheets/application.scss | 1 + .../modules/subscriptions/renew-modal.scss | 24 ++++ .../templates/admin/members/edit.html | 9 +- config/locales/app.admin.en.yml | 16 ++- 8 files changed, 187 insertions(+), 13 deletions(-) create mode 100644 app/frontend/src/javascript/components/subscriptions/renew-modal.tsx create mode 100644 app/frontend/src/stylesheets/modules/subscriptions/renew-modal.scss diff --git a/app/frontend/src/javascript/components/payment-schedule/payment-schedule-summary.tsx b/app/frontend/src/javascript/components/payment-schedule/payment-schedule-summary.tsx index 84800ec60..342d425c5 100644 --- a/app/frontend/src/javascript/components/payment-schedule/payment-schedule-summary.tsx +++ b/app/frontend/src/javascript/components/payment-schedule/payment-schedule-summary.tsx @@ -17,7 +17,7 @@ interface PaymentScheduleSummaryProps { /** * This component displays a summary of the monthly payment schedule for the current cart, with a subscription */ -const PaymentScheduleSummary: React.FC = ({ schedule }) => { +export const PaymentScheduleSummary: React.FC = ({ schedule }) => { const { t } = useTranslation('shared'); // is open, the modal dialog showing the full details of the payment schedule? diff --git a/app/frontend/src/javascript/components/payment-schedule/select-schedule.tsx b/app/frontend/src/javascript/components/payment-schedule/select-schedule.tsx index bff5e0b52..03419ab61 100644 --- a/app/frontend/src/javascript/components/payment-schedule/select-schedule.tsx +++ b/app/frontend/src/javascript/components/payment-schedule/select-schedule.tsx @@ -12,19 +12,19 @@ interface SelectScheduleProps { show: boolean, selected: boolean, onChange: (selected: boolean) => void, - className: string, + className?: string, } /** * This component is a switch enabling the users to choose if they want to pay by monthly schedule * or with a one time payment */ -const SelectSchedule: React.FC = ({ show, selected, onChange, className }) => { +export const SelectSchedule: React.FC = ({ show, selected, onChange, className }) => { const { t } = useTranslation('shared'); return (
- {show &&
+ {show &&
} diff --git a/app/frontend/src/javascript/components/subscriptions/renew-modal.tsx b/app/frontend/src/javascript/components/subscriptions/renew-modal.tsx new file mode 100644 index 000000000..865de5275 --- /dev/null +++ b/app/frontend/src/javascript/components/subscriptions/renew-modal.tsx @@ -0,0 +1,127 @@ +import React, { useEffect, useState } from 'react'; +import { Subscription, SubscriptionPaymentDetails } from '../../models/subscription'; +import { FabModal, ModalSize } from '../base/fab-modal'; +import { useTranslation } from 'react-i18next'; +import { FabAlert } from '../base/fab-alert'; +import { FabInput } from '../base/fab-input'; +import FormatLib from '../../lib/format'; +import { Loader } from '../base/loader'; +import { react2angular } from 'react2angular'; +import { IApplication } from '../../models/application'; +import LocalPaymentAPI from '../../api/local-payment'; +import { PaymentMethod, ShoppingCart } from '../../models/payment'; +import moment from 'moment'; +import { SelectSchedule } from '../payment-schedule/select-schedule'; +import SubscriptionAPI from '../../api/subscription'; +import { PaymentScheduleSummary } from '../payment-schedule/payment-schedule-summary'; + +declare const Application: IApplication; + +interface RenewModalProps { + isOpen: boolean, + toggleModal: () => void, + subscription?: Subscription, + customerId: number, + onSuccess: (message: string, newExpirationDate: Date) => void, + onError: (message: string) => void, +} + +/** + * Modal dialog shown to renew the current subscription of a customer, for free + */ +const RenewModal: React.FC = ({ isOpen, toggleModal, subscription, customerId, onError, onSuccess }) => { + const { t } = useTranslation('admin'); + + const [expirationDate, setExpirationDate] = useState(new Date()); + const [localPaymentModal, setLocalPaymentModal] = useState(false); + const [cart, setCart] = useState(null); + const [scheduleRequired, setScheduleRequired] = useState(false); + + // on init, we compute the new expiration date + useEffect(() => { + if (!subscription) return; + + setExpirationDate(moment(subscription.expired_at) + .add(subscription.plan.interval_count, subscription.plan.interval) + .toDate()); + SubscriptionAPI.paymentsDetails(subscription.id) + .then(res => setScheduleRequired(res.payment_schedule)) + .catch(err => onError(err)); + }, []); + + /** + * Return the formatted localized date for the given date + */ + const formatDateTime = (date: Date): string => { + return t('app.admin.free_extend_modal.DATE_TIME', { DATE: FormatLib.date(date), TIME: FormatLib.time(date) }); + }; + + /** + * Callback triggered when the user validates the renew of the subscription + */ + const handleConfirmRenew = (): void => { + LocalPaymentAPI.confirmPayment({ + customer_id: customerId, + payment_method: PaymentMethod.Other, + items: [ + { + subscription: { + plan_id: subscription.plan_id + // start_at: subscription.expired_at + } + } + ] + }).then(() => { + onSuccess(t('app.admin.renew_subscription_modal.renew_success'), expirationDate); + toggleModal(); + }).catch(err => onError(err)); + }; + + // we do not render the modal if the subscription was not provided + if (!subscription) return null; + + return ( + + +

{t('app.admin.renew_subscription_modal.renew_subscription_info')}

+

{t('app.admin.renew_subscription_modal.credits_will_be_reset')}

+
+
+
+ + + + + + + +
+ +
+
+
+ ); +}; + +const RenewModalWrapper: React.FC = ({ toggleModal, subscription, customerId, isOpen, onSuccess, onError }) => { + return ( + + + + ); +}; + +Application.Components.component('renewModal', react2angular(RenewModalWrapper, ['toggleModal', 'subscription', 'customerId', 'isOpen', 'onError', 'onSuccess'])); diff --git a/app/frontend/src/javascript/controllers/admin/members.js b/app/frontend/src/javascript/controllers/admin/members.js index dcb5d775a..0777c5bdd 100644 --- a/app/frontend/src/javascript/controllers/admin/members.js +++ b/app/frontend/src/javascript/controllers/admin/members.js @@ -705,9 +705,12 @@ Application.Controllers.controller('EditMemberController', ['$scope', '$state', // current active authentication provider $scope.activeProvider = activeProviderPromise; - // modal dialog to extend the current subscription for free + // modal dialog to extend the current subscription for free $scope.isOpenFreeExtendModal = false; + // modal dialog to renew the current subscription + $scope.isOpenRenewModal = false; + /** * Open a modal dialog asking for confirmation to change the role of the given user * @returns {*} @@ -765,6 +768,16 @@ Application.Controllers.controller('EditMemberController', ['$scope', '$state', }, 50); }; + /** + * Opens/closes the modal dialog to renew the subscription (with payment) + */ + $scope.toggleRenewModal = () => { + setTimeout(() => { + $scope.isOpenRenewModal = !$scope.isOpenRenewModal; + $scope.$apply(); + }, 50); + }; + /** * Callback triggered if the subscription was successfully extended */ diff --git a/app/frontend/src/stylesheets/application.scss b/app/frontend/src/stylesheets/application.scss index 84c0cee14..13203a952 100644 --- a/app/frontend/src/stylesheets/application.scss +++ b/app/frontend/src/stylesheets/application.scss @@ -66,5 +66,6 @@ @import "modules/prepaid-packs/propose-packs-modal"; @import "modules/prepaid-packs/packs-summary"; @import "modules/subscriptions/free-extend-modal"; +@import "modules/subscriptions/renew-modal"; @import "app.responsive"; diff --git a/app/frontend/src/stylesheets/modules/subscriptions/renew-modal.scss b/app/frontend/src/stylesheets/modules/subscriptions/renew-modal.scss new file mode 100644 index 000000000..111a7909c --- /dev/null +++ b/app/frontend/src/stylesheets/modules/subscriptions/renew-modal.scss @@ -0,0 +1,24 @@ +.renew-modal { + .fab-modal-content { + padding: 30px; + + .form-and-payment { + display: flex; + + .configuration-form { + padding-right: 15px; + + .input-wrapper { + display: block; + } + } + + .payment { + border: 1px solid #ccc; + border-radius: 2px; + padding: 10px; + width: 50%; + } + } + } +} diff --git a/app/frontend/templates/admin/members/edit.html b/app/frontend/templates/admin/members/edit.html index b9f4a68a9..e75040114 100644 --- a/app/frontend/templates/admin/members/edit.html +++ b/app/frontend/templates/admin/members/edit.html @@ -78,7 +78,7 @@

- + + +

{{ 'app.admin.members_edit.cannot_extend_own_subscription' }} diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 93b2aaa68..31854678b 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -888,13 +888,6 @@ en: download_the_invoice: "Download the invoice" download_the_refund_invoice: "Download the refund invoice" no_invoices_for_now: "No invoices for now." - current_expiration_date: "Current subscription will expire at:" - expiration_date: "Change subscription expiration date" - new_subscription_start: "The new subscription will start at:" - payment_schedule_card: "The previous subscription was charged by card through a payment schedule, this one will be charged the same way. The first deadline will be charged when the current subscription expires, then each following month." - payment_schedule_check: "The previous subscription was charged by check through a payment schedule, this one will be charged the same way. Before confirming please ensure you have all the checks to collect all the monthly payments." - one_payment_card: "The previous subscription was charged by card through a single payment, this one will be charged the same way. The payment will be charged right now." - one_payment_check: "The previous subscription was charged by check through a single payment, this one will be charged the same way. Before confirming please ensure you have collected the payment." you_successfully_changed_the_expiration_date_of_the_user_s_subscription: "You successfully changed the expiration date of the user's subscription" a_problem_occurred_while_saving_the_date: "A problem occurred while saving the date." new_subscription: "New subscription" @@ -922,6 +915,15 @@ en: renew_subscription: "Renew the subscription" renew_subscription_info: "You are about to renew the user's subscription by charging him again for his current subscription." credits_will_be_reset: "The balance of free credits (training / machines / spaces) of the user will be reset, unused credits will be lost." + current_expiration: "Current subscription will expire at:" + new_start: "The new subscription will start at:" + new_expiration_date: "The new subscription will expire at:" + payment_schedule_card: "The previous subscription was charged by card through a payment schedule, this one will be charged the same way. The first deadline will be charged when the current subscription expires, then each following month." + payment_schedule_check: "The previous subscription was charged by check through a payment schedule, this one will be charged the same way. Before confirming please ensure you have all the checks to collect all the monthly payments." + one_payment_card: "The previous subscription was charged by card through a single payment, this one will be charged the same way. The payment will be charged right now." + one_payment_check: "The previous subscription was charged by check through a single payment, this one will be charged the same way. Before confirming please ensure you have collected the payment." + renew: "Renew" + renew_success: "The subscription was successfully renewed" #add a new administrator to the platform admins_new: add_an_administrator: "Add an administrator"