payment
parent
f646a19cdf
commit
6694a48b44
|
@ -20,6 +20,7 @@
|
|||
"hour": "Stunde",
|
||||
"minutes": "Minuten",
|
||||
"minute": "Minute",
|
||||
"daysLong": "Tagen",
|
||||
"days": "Tage",
|
||||
"day": "Tag",
|
||||
"separator": "und"
|
||||
|
@ -507,13 +508,38 @@
|
|||
]
|
||||
},
|
||||
"paymentPlan": {
|
||||
"title": "Zahlungsplan",
|
||||
"buttonUpdateBillingDetails": "Zahlungsdetails aktualisieren",
|
||||
"plan": "Plan",
|
||||
"contentsOfSubscription": {
|
||||
"title": "Inhalt des Abonnements",
|
||||
"maxEmployees": "Maximale Anzahl von Mitarbeitern",
|
||||
"calendarMaxFutureBookingDays": "Maximale Anzahl von Tagen im Voraus"
|
||||
}
|
||||
"title": "Features & Preisübersicht",
|
||||
"buttonMonthly": "Monatlich",
|
||||
"buttonYearly": "Jährlich",
|
||||
"buttonManagePlan": "Zahlungsplan verwalten",
|
||||
"buttonBuyPlan": "Jetzt buchen",
|
||||
"monthly": "monatlich",
|
||||
"businessPlan": "Unternehmensplan",
|
||||
"cancelledOn": "Gekündigt am {{date}} Uhr",
|
||||
"changeToAnnualPayment": "Wechseln Sie zur jährlichen Zahlung und ",
|
||||
"saveAmount": "sparen Sie {{amount}} €",
|
||||
"popconfirmChangePlan": {
|
||||
"title": "Zahlungsplan wechseln",
|
||||
"description": "Sind Sie sicher, dass Sie Ihren Plan ändern wollen?"
|
||||
},
|
||||
"alertDemoEndsIn": {
|
||||
"demoEndsIn": "Die Testversion endet in",
|
||||
"selectPlanToAccessFullFeatures": "Wählen Sie einen Plan, um auf alle Funktionen zuzugreifen."
|
||||
},
|
||||
"alertPlanCancelled": "Ihr Plan wurde gekündigt. Am Ende des Abrechnungszeitraums müssen Sie einen neuen Plan auswählen, um unsere Dienste weiterhin nutzen zu können.",
|
||||
"features": [
|
||||
{
|
||||
"text": "Unbegrenzte <b>{{bold}}</b>",
|
||||
"bold": "Terminanzahl"
|
||||
},
|
||||
{
|
||||
"text": "Bis zu <b>{{bold}}</b>",
|
||||
"bold": "20 Mitarbeiter"
|
||||
},
|
||||
{
|
||||
"text": "Buchungen bis zu <b>{{bold}}</b> im Voraus",
|
||||
"bold": "7 Wochen"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"hour": "hour",
|
||||
"minutes": "minutes",
|
||||
"minute": "minute",
|
||||
"daysLong": "days",
|
||||
"days": "days",
|
||||
"day": "day",
|
||||
"separator": "and"
|
||||
|
@ -516,13 +517,38 @@
|
|||
]
|
||||
},
|
||||
"paymentPlan": {
|
||||
"title": "Payment plan",
|
||||
"buttonUpdateBillingDetails": "Update billing details",
|
||||
"plan": "Plan",
|
||||
"contentsOfSubscription": {
|
||||
"title": "Contents of subscription",
|
||||
"maxEmployees": "Max. employees",
|
||||
"calendarMaxFutureBookingDays": "Max. future booking days"
|
||||
}
|
||||
"title": "Features & Pricing",
|
||||
"buttonMonthly": "Monthly",
|
||||
"buttonYearly": "Yearly",
|
||||
"buttonManagePlan": "Manage plan",
|
||||
"buttonBuyPlan": "Buy now",
|
||||
"monthly": "monthly",
|
||||
"businessPlan": "Business Plan",
|
||||
"cancelledOn": "Cancelled on {{date}} o'clock",
|
||||
"changeToAnnualPayment": "Switch to annual payment and ",
|
||||
"saveAmount": "save {{amount}} €",
|
||||
"popconfirmChangePlan": {
|
||||
"title": "Change plan",
|
||||
"description": "Are you sure you want to change your plan?"
|
||||
},
|
||||
"alertDemoEndsIn": {
|
||||
"demoEndsIn": "Demo ends in",
|
||||
"selectPlanToAccessFullFeatures": "Select a plan to access all features."
|
||||
},
|
||||
"alertPlanCancelled": "Your plan has been cancelled. At the end of the billing period, you need to select a new plan to continue using our services.",
|
||||
"features": [
|
||||
{
|
||||
"text": "Unlimited <b>{{bold}}</b>",
|
||||
"bold": "number of appointments"
|
||||
},
|
||||
{
|
||||
"text": "Up to <b>{{bold}}</b>",
|
||||
"bold": "20 employees"
|
||||
},
|
||||
{
|
||||
"text": "Bookings up to <b>{{bold}}</b> in advance",
|
||||
"bold": "7 weeks"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -174,17 +174,10 @@ export function SideMenuContent({
|
|||
}
|
||||
}, [location.pathname]);
|
||||
|
||||
const calculateExpiry = () => {
|
||||
const currentDate = new Date();
|
||||
const expiryDate = new Date(sideBarContext.paymentPlanTrialEnd);
|
||||
|
||||
const diffTime = Math.abs(expiryDate - currentDate);
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
return diffDays;
|
||||
};
|
||||
|
||||
const accountPlanExpiry = calculateExpiry();
|
||||
const daysLeft = Math.floor(
|
||||
(new Date(sideBarContext.paymentPlanTrialEnd) - new Date()) /
|
||||
(1000 * 60 * 60 * 24)
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -254,9 +247,9 @@ export function SideMenuContent({
|
|||
style={{ color: "#fff", textAlign: "center" }}
|
||||
>
|
||||
{t("sideMenu.paymentPlanTrailingDaysLeft", {
|
||||
daysLeft: accountPlanExpiry,
|
||||
daysLeft: daysLeft,
|
||||
dayUnit:
|
||||
accountPlanExpiry > 1
|
||||
daysLeft > 1
|
||||
? t("common.unit.days")
|
||||
: t("common.unit.day"),
|
||||
})}
|
||||
|
|
|
@ -10,25 +10,26 @@ import {
|
|||
Grid,
|
||||
Skeleton,
|
||||
notification,
|
||||
Popconfirm,
|
||||
Tag,
|
||||
} from "antd";
|
||||
import { useAppContext } from "../../Contexts/AppContext";
|
||||
import {
|
||||
AppStyle,
|
||||
FormatDatetime,
|
||||
myFetch,
|
||||
showUnkownErrorNotification,
|
||||
} from "../../utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { CheckOutlined, CloseOutlined } from "@ant-design/icons";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ReactComponent as RocketLaunch } from "./rocket_launch_FILL1_wght400_GRAD0_opsz24.svg";
|
||||
import { ReactComponent as Check } from "./task_alt_FILL0_wght400_GRAD0_opsz24.svg";
|
||||
import CountUp from "react-countup";
|
||||
|
||||
const { useBreakpoint } = Grid;
|
||||
|
||||
export default function PaymentPlan() {
|
||||
const { t } = useTranslation();
|
||||
const appContext = useAppContext();
|
||||
|
||||
const [notificationApi, notificationContextHolder] =
|
||||
notification.useNotification();
|
||||
|
@ -62,8 +63,7 @@ export default function PaymentPlan() {
|
|||
setSelectedBillingPeriod(data.payment_plan_interval);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
.catch(() => {
|
||||
setIsRequesting(false);
|
||||
showUnkownErrorNotification(notificationApi, t);
|
||||
});
|
||||
|
@ -71,19 +71,31 @@ export default function PaymentPlan() {
|
|||
|
||||
useEffect(() => fetchPaymentPlan(), []);
|
||||
|
||||
const showAlert = () => {
|
||||
const showAlertTrialEnds = () => {
|
||||
if (
|
||||
!isRequesting &&
|
||||
requestData.payment_plan === 0 &&
|
||||
requestData.payment_plan_trial_end &&
|
||||
!requestData.payment_plan_canceled_at
|
||||
) {
|
||||
const daysLeft = Math.floor(
|
||||
(new Date(requestData.payment_plan_trial_end) - new Date()) /
|
||||
(1000 * 60 * 60 * 24)
|
||||
);
|
||||
|
||||
return (
|
||||
<Alert
|
||||
message={
|
||||
<Typography.Text>
|
||||
Ihre Demo endet in <b>7 Tagen</b>. Jetzt einen Plan auswählen, um
|
||||
den vollen Funktionsumfang zu nutzen.
|
||||
{t("paymentPlan.alertDemoEndsIn.demoEndsIn")}{" "}
|
||||
<b>
|
||||
{daysLeft}{" "}
|
||||
{daysLeft === 1
|
||||
? t("common.unit.day")
|
||||
: t("common.unit.daysLong")}
|
||||
</b>
|
||||
.{" "}
|
||||
{t("paymentPlan.alertDemoEndsIn.selectPlanToAccessFullFeatures")}
|
||||
</Typography.Text>
|
||||
}
|
||||
type="warning"
|
||||
|
@ -95,34 +107,104 @@ export default function PaymentPlan() {
|
|||
}
|
||||
};
|
||||
|
||||
const showAlertPlanCancelled = () => {
|
||||
if (!isRequesting && requestData.payment_plan_canceled_at) {
|
||||
return (
|
||||
<Alert
|
||||
message={
|
||||
<Typography.Text>
|
||||
{t("paymentPlan.alertPlanCancelled", {
|
||||
date: FormatDatetime(requestData.payment_plan_canceled_at),
|
||||
})}
|
||||
</Typography.Text>
|
||||
}
|
||||
type="warning"
|
||||
showIcon
|
||||
closable
|
||||
style={{ marginBottom: 12 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const percentageDiscount =
|
||||
requestData.prices.length > 0
|
||||
? Math.round(
|
||||
100 -
|
||||
(100 / (requestData.prices[0].unit_amount * 12)) *
|
||||
requestData.prices[1].unit_amount
|
||||
)
|
||||
: 0;
|
||||
|
||||
const handleButtonBuyPlan = () => {
|
||||
setRequestingCheckout(true);
|
||||
|
||||
myFetch({
|
||||
method: "POST",
|
||||
url: "/payment/checkout",
|
||||
body: {
|
||||
lookupKey: `za-basic-${
|
||||
selectedBillingPeriod === 0 ? "monthly" : "yearly"
|
||||
}`,
|
||||
},
|
||||
notificationApi,
|
||||
t,
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.url) {
|
||||
window.location.href = data.url;
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.status === "ok") {
|
||||
fetchPaymentPlan();
|
||||
setRequestingCheckout(false);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
setRequestingCheckout(false);
|
||||
});
|
||||
};
|
||||
|
||||
const ButtonBuyPlan = ({ onClick }) => {
|
||||
return (
|
||||
<Button
|
||||
loading={requestingCheckout}
|
||||
shape="round"
|
||||
style={{ fontWeight: "bold" }}
|
||||
onClick={onClick}
|
||||
>
|
||||
{t("paymentPlan.buttonBuyPlan")}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{notificationContextHolder}
|
||||
|
||||
{showAlert()}
|
||||
{showAlertTrialEnds()}
|
||||
{showAlertPlanCancelled()}
|
||||
|
||||
<Card title="Features & Preisübersicht">
|
||||
<Card title={t("paymentPlan.title")}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col xs={24} xl={12}>
|
||||
<Flex vertical>
|
||||
<Space>
|
||||
<Check />
|
||||
<Typography.Text style={{ fontSize: 20 }}>
|
||||
Unbegrenzte <b>Terminanzahl</b>
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
<Space>
|
||||
<Check />
|
||||
<Typography.Text style={{ fontSize: 20 }}>
|
||||
Bis zu <b>20 Mitarbeiter</b>
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
<Space>
|
||||
<Check />
|
||||
<Typography.Text style={{ fontSize: 20 }}>
|
||||
Buchungen bis zu <b>6 Wochen</b> im Voraus
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
{t("paymentPlan.features", { returnObjects: true }).map(
|
||||
(feature, index) => (
|
||||
<Space key={index}>
|
||||
<Check />
|
||||
<Typography.Text style={{ fontSize: 20 }}>
|
||||
<Trans
|
||||
i18nKey={feature.text}
|
||||
values={{ bold: feature.bold }}
|
||||
components={{ b: <b /> }}
|
||||
/>
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
)
|
||||
)}
|
||||
</Flex>
|
||||
</Col>
|
||||
|
||||
|
@ -140,12 +222,12 @@ export default function PaymentPlan() {
|
|||
(requestData.payment_plan_canceled_at === undefined ? (
|
||||
<CheckOutlined />
|
||||
) : (
|
||||
<CloseOutlined />
|
||||
<CloseOutlined style={{ color: "#d10205" }} />
|
||||
))
|
||||
}
|
||||
onClick={() => setSelectedBillingPeriod(0)}
|
||||
>
|
||||
Monatlich
|
||||
{t("paymentPlan.buttonMonthly")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
|
@ -160,12 +242,29 @@ export default function PaymentPlan() {
|
|||
(requestData.payment_plan_canceled_at === undefined ? (
|
||||
<CheckOutlined />
|
||||
) : (
|
||||
<CloseOutlined />
|
||||
<CloseOutlined style={{ color: "#d10205" }} />
|
||||
))
|
||||
}
|
||||
onClick={() => setSelectedBillingPeriod(1)}
|
||||
>
|
||||
Jährlich
|
||||
<Space>
|
||||
<span>{t("paymentPlan.buttonYearly")}</span>
|
||||
|
||||
{requestData.payment_plan_interval !== 1 && (
|
||||
<Tag color="green">
|
||||
-
|
||||
<CountUp
|
||||
start={
|
||||
percentageDiscount > 10
|
||||
? percentageDiscount - 10
|
||||
: 0
|
||||
}
|
||||
end={percentageDiscount}
|
||||
/>{" "}
|
||||
%
|
||||
</Tag>
|
||||
)}
|
||||
</Space>
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
|
@ -197,74 +296,56 @@ export default function PaymentPlan() {
|
|||
level={screenBreakpoint.xs ? 3 : 2}
|
||||
style={{ color: "#fff" }}
|
||||
>
|
||||
Unternehmensplan
|
||||
{t("paymentPlan.businessPlan")}
|
||||
</Typography.Title>
|
||||
|
||||
{isRequesting ? (
|
||||
<Skeleton.Button shape="round" block active />
|
||||
) : (
|
||||
<Button
|
||||
loading={requestingCheckout}
|
||||
shape="round"
|
||||
style={{ fontWeight: "bold" }}
|
||||
onClick={() => {
|
||||
setRequestingCheckout(true);
|
||||
|
||||
if (
|
||||
selectedBillingPeriod ===
|
||||
requestData.payment_plan_interval
|
||||
) {
|
||||
myFetch({
|
||||
method: "GET",
|
||||
url: "/payment/portal",
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.url) {
|
||||
window.location.href = data.url;
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
setRequestingCheckout(false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
myFetch({
|
||||
method: "POST",
|
||||
url: "/payment/checkout",
|
||||
body: {
|
||||
lookupKey: `za-basic-${
|
||||
selectedBillingPeriod === 0 ? "monthly" : "yearly"
|
||||
}`,
|
||||
},
|
||||
notificationApi,
|
||||
t,
|
||||
})
|
||||
.then((data) => {
|
||||
console.log(data);
|
||||
|
||||
if (data.url) {
|
||||
window.location.href = data.url;
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.status === "ok") {
|
||||
fetchPaymentPlan();
|
||||
setRequestingCheckout(false);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
setRequestingCheckout(false);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<>
|
||||
{selectedBillingPeriod ===
|
||||
requestData.payment_plan_interval
|
||||
? "Plan verwalten"
|
||||
: "Jetzt buchen"}
|
||||
</Button>
|
||||
requestData.payment_plan_interval ? (
|
||||
<Button
|
||||
loading={requestingCheckout}
|
||||
shape="round"
|
||||
style={{ fontWeight: "bold" }}
|
||||
onClick={() => {
|
||||
setRequestingCheckout(true);
|
||||
|
||||
myFetch({
|
||||
method: "GET",
|
||||
url: "/payment/portal",
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.url) {
|
||||
window.location.href = data.url;
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
setRequestingCheckout(false);
|
||||
});
|
||||
return;
|
||||
}}
|
||||
>
|
||||
{t("paymentPlan.buttonManagePlan")}
|
||||
</Button>
|
||||
) : requestData.payment_plan === 0 ? (
|
||||
<ButtonBuyPlan onClick={() => handleButtonBuyPlan()} />
|
||||
) : (
|
||||
<Popconfirm
|
||||
title={t("paymentPlan.popconfirmChangePlan.title")}
|
||||
description={t(
|
||||
"paymentPlan.popconfirmChangePlan.description"
|
||||
)}
|
||||
okText={t("common.button.confirm")}
|
||||
cancelText={t("common.button.cancel")}
|
||||
onConfirm={() => handleButtonBuyPlan()}
|
||||
>
|
||||
<ButtonBuyPlan />
|
||||
</Popconfirm>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
|
@ -296,18 +377,40 @@ export default function PaymentPlan() {
|
|||
<Typography.Text
|
||||
style={{ color: "#fff", textAlign: "center" }}
|
||||
>
|
||||
/monatlich
|
||||
/{t("paymentPlan.monthly")}
|
||||
</Typography.Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card>
|
||||
|
||||
{selectedBillingPeriod === requestData.payment_plan &&
|
||||
{selectedBillingPeriod === requestData.payment_plan_interval &&
|
||||
requestData.payment_plan_canceled_at !== undefined && (
|
||||
<Flex justify="center">
|
||||
<Typography.Text type="secondary">
|
||||
Gekündigt am{" "}
|
||||
{FormatDatetime(requestData.payment_plan_canceled_at)} Uhr
|
||||
{t("paymentPlan.cancelledOn", {
|
||||
date: FormatDatetime(
|
||||
requestData.payment_plan_canceled_at
|
||||
),
|
||||
})}
|
||||
</Typography.Text>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{selectedBillingPeriod === 1 &&
|
||||
requestData.payment_plan_interval !== 1 && (
|
||||
<Flex justify="center">
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
style={{ color: "#27ae60" }}
|
||||
>
|
||||
{t("paymentPlan.changeToAnnualPayment")}
|
||||
<b>
|
||||
{t("paymentPlan.saveAmount", {
|
||||
amount:
|
||||
requestData.prices[0].unit_amount * 12 -
|
||||
requestData.prices[1].unit_amount,
|
||||
})}
|
||||
</b>
|
||||
</Typography.Text>
|
||||
</Flex>
|
||||
)}
|
||||
|
|
Loading…
Reference in New Issue