Remove Intl.Provider (#8208)
* Add payment option to schema * Add payment option to Stripe zod * Set payment option on event type * Create manual payment intent in Stripe * Set payment option from Stripe app * Add payment option to DB * Pass React.ReactNode to checkbox * Create uncaptured payment intent * WIP * Capture card in setup intent * Show charge card option * Charge card from booking page * Bug fixes * Clean up * Clean up app card * Add no-show fee messaging on booking page * Send payment email on payment & add price * Fix messaging * Create no show fee charged email * Send charge fee collected email * Disable submit on card failure * Clean up * Serverside prevent charging card again if already charged * Only confirm booking if paid for * Type fixes * More type fixes * More type fixes * Type fix * Type fixes * UI changes * Payment component rework * Update apps/web/public/static/locales/en/common.json Co-authored-by: Alex van Andel <me@alexvanandel.com> * Update apps/web/public/static/locales/en/common.json Co-authored-by: Alex van Andel <me@alexvanandel.com> * Update apps/web/components/dialog/ChargeCardDialog.tsx Co-authored-by: Alex van Andel <me@alexvanandel.com> * Update packages/trpc/server/routers/viewer/payments.tsx Co-authored-by: Alex van Andel <me@alexvanandel.com> * Revert GTM config * Adjust payment option dropdown * Show alert when seats are set * Small bug fixes * Create collect card method * clean up * Prevent seats & charge no-show fee to be enabled together * Do not charge no-show fee on unconfirmed bookings * Add check to collect card method * Webhook send request emails * Fix some dark mode colours * Change awaiting payment language * Type fixes * Set height of Select and TextField both to 38px to fix alignment * Fix message seats & payment error message * Type fix * Remove Intl.Provider * Add percentFeePercentage * WIP * Use i18n language * Type fix --------- Co-authored-by: Alex van Andel <me@alexvanandel.com> Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
co-authored by
Alex van Andel
Omar López
parent
b1ff829745
commit
b0530d59af
@@ -41,7 +41,7 @@ const Component = ({
|
|||||||
isTemplate,
|
isTemplate,
|
||||||
dependencies,
|
dependencies,
|
||||||
}: Parameters<typeof App>[0]) => {
|
}: Parameters<typeof App>[0]) => {
|
||||||
const { t } = useLocale();
|
const { t, i18n } = useLocale();
|
||||||
const hasDescriptionItems = descriptionItems && descriptionItems.length > 0;
|
const hasDescriptionItems = descriptionItems && descriptionItems.length > 0;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -247,7 +247,7 @@ const Component = ({
|
|||||||
t("free_to_use_apps")
|
t("free_to_use_apps")
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{Intl.NumberFormat("en-US", {
|
{Intl.NumberFormat(i18n.language, {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
useGrouping: false,
|
useGrouping: false,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { i18n } from "next-i18next";
|
||||||
import type { TFunction } from "next-i18next";
|
import type { TFunction } from "next-i18next";
|
||||||
import { FormattedNumber, IntlProvider } from "react-intl";
|
|
||||||
|
|
||||||
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
|
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
|
||||||
import { CreditCard } from "@calcom/ui/components/icon";
|
import { CreditCard } from "@calcom/ui/components/icon";
|
||||||
@@ -7,28 +7,29 @@ import { CreditCard } from "@calcom/ui/components/icon";
|
|||||||
const BookingDescriptionPayment = (props: {
|
const BookingDescriptionPayment = (props: {
|
||||||
eventType: Parameters<typeof getPaymentAppData>[0];
|
eventType: Parameters<typeof getPaymentAppData>[0];
|
||||||
t: TFunction;
|
t: TFunction;
|
||||||
|
i18n: typeof i18n;
|
||||||
}) => {
|
}) => {
|
||||||
const paymentAppData = getPaymentAppData(props.eventType);
|
const paymentAppData = getPaymentAppData(props.eventType);
|
||||||
if (!paymentAppData || paymentAppData.price <= 0) return null;
|
if (!paymentAppData || paymentAppData.price <= 0) return null;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
amount: paymentAppData.price / 100.0,
|
||||||
|
formatParams: { amount: { currency: paymentAppData.currency } },
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p className="text-bookinglight -ml-2 px-2 text-sm ">
|
<p className="text-bookinglight -ml-2 px-2 text-sm ">
|
||||||
<CreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
<CreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
||||||
{paymentAppData.paymentOption === "HOLD" ? (
|
{paymentAppData.paymentOption === "HOLD" ? (
|
||||||
<>
|
<>{props.t("no_show_fee_amount", params)}</>
|
||||||
{props.t("no_show_fee_amount", {
|
|
||||||
amount: paymentAppData.price / 100.0,
|
|
||||||
formatParams: { amount: { currency: paymentAppData.currency } },
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<IntlProvider locale="en">
|
<>
|
||||||
<FormattedNumber
|
{/* If undefined this will default to the browser locale */}
|
||||||
value={paymentAppData.price / 100.0}
|
{new Intl.NumberFormat(i18n?.language, {
|
||||||
style="currency"
|
style: "currency",
|
||||||
currency={paymentAppData.currency?.toUpperCase()}
|
currency: paymentAppData.currency,
|
||||||
/>
|
}).format(paymentAppData.price / 100)}
|
||||||
</IntlProvider>
|
</>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useMemo, useReducer, useState } from "react";
|
import { useEffect, useMemo, useReducer, useState } from "react";
|
||||||
import { FormattedNumber, IntlProvider } from "react-intl";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import BookingPageTagManager from "@calcom/app-store/BookingPageTagManager";
|
import BookingPageTagManager from "@calcom/app-store/BookingPageTagManager";
|
||||||
@@ -75,7 +74,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
|||||||
brandColor: profile.brandColor,
|
brandColor: profile.brandColor,
|
||||||
darkBrandColor: profile.darkBrandColor,
|
darkBrandColor: profile.darkBrandColor,
|
||||||
});
|
});
|
||||||
const { t } = useLocale();
|
const { t, i18n } = useLocale();
|
||||||
const availabilityDatePickerEmbedStyles = useEmbedStyles("availabilityDatePicker");
|
const availabilityDatePickerEmbedStyles = useEmbedStyles("availabilityDatePicker");
|
||||||
//TODO: Plan to remove shouldAlignCentrallyInEmbed config
|
//TODO: Plan to remove shouldAlignCentrallyInEmbed config
|
||||||
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
|
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
|
||||||
@@ -124,16 +123,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
|||||||
[timeZone]
|
[timeZone]
|
||||||
);
|
);
|
||||||
const paymentAppData = getPaymentAppData(eventType);
|
const paymentAppData = getPaymentAppData(eventType);
|
||||||
const paymentAmount = () => {
|
|
||||||
return;
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<FormattedNumber
|
|
||||||
value={paymentAppData.price / 100.0}
|
|
||||||
style="currency"
|
|
||||||
currency={paymentAppData.currency?.toUpperCase()}
|
|
||||||
/>
|
|
||||||
</IntlProvider>;
|
|
||||||
};
|
|
||||||
const rainbowAppData = getEventTypeAppData(eventType, "rainbow") || {};
|
const rainbowAppData = getEventTypeAppData(eventType, "rainbow") || {};
|
||||||
const rawSlug = profile.slug ? profile.slug.split("/") : [];
|
const rawSlug = profile.slug ? profile.slug.split("/") : [];
|
||||||
if (rawSlug.length > 1) rawSlug.pop(); //team events have team name as slug, but user events have [user]/[type] as slug.
|
if (rawSlug.length > 1) rawSlug.pop(); //team events have team name as slug, but user events have [user]/[type] as slug.
|
||||||
@@ -257,13 +247,12 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
|||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<IntlProvider locale="en">
|
<>
|
||||||
<FormattedNumber
|
{new Intl.NumberFormat(i18n.language, {
|
||||||
value={paymentAppData.price / 100.0}
|
style: "currency",
|
||||||
style="currency"
|
currency: paymentAppData.currency,
|
||||||
currency={paymentAppData.currency?.toUpperCase()}
|
}).format(paymentAppData.price / 100)}
|
||||||
/>
|
</>
|
||||||
</IntlProvider>
|
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -554,7 +554,7 @@ const BookingPage = ({
|
|||||||
{showEventTypeDetails && (
|
{showEventTypeDetails && (
|
||||||
<div className="sm:border-subtle text-default flex flex-col px-6 pt-6 pb-0 sm:w-1/2 sm:border-r sm:pb-6">
|
<div className="sm:border-subtle text-default flex flex-col px-6 pt-6 pb-0 sm:w-1/2 sm:border-r sm:pb-6">
|
||||||
<BookingDescription isBookingPage profile={profile} eventType={eventType}>
|
<BookingDescription isBookingPage profile={profile} eventType={eventType}>
|
||||||
<BookingDescriptionPayment eventType={eventType} t={t} />
|
<BookingDescriptionPayment eventType={eventType} t={t} i18n={i18n} />
|
||||||
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
|
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
|
||||||
<div className="dark:text-inverted text-default items-start text-sm font-medium">
|
<div className="dark:text-inverted text-default items-start text-sm font-medium">
|
||||||
<RefreshCw className="ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
<RefreshCw className="ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { Trans } from "next-i18next";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import type { Dispatch, SetStateAction } from "react";
|
import type { Dispatch, SetStateAction } from "react";
|
||||||
import { IntlProvider, FormattedNumber } from "react-intl";
|
|
||||||
|
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
@@ -40,6 +38,11 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const currencyStringParams = {
|
||||||
|
amount: props.paymentAmount / 100.0,
|
||||||
|
formatParams: { amount: { currency: props.paymentCurrency } },
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}>
|
<Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
@@ -49,19 +52,7 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="pt-1">
|
<div className="pt-1">
|
||||||
<DialogHeader title={t("charge_card")} />
|
<DialogHeader title={t("charge_card")} />
|
||||||
<Trans i18nKey="charge_card_dialog_body">
|
<p>{t("charge_card_dialog_body", currencyStringParams)}</p>
|
||||||
<p className="text-sm text-gray-500">
|
|
||||||
You are about to charge the attendee{" "}
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<FormattedNumber
|
|
||||||
value={props.paymentAmount / 100.0}
|
|
||||||
style="currency"
|
|
||||||
currency={props.paymentCurrency?.toUpperCase()}
|
|
||||||
/>
|
|
||||||
</IntlProvider>
|
|
||||||
. Are you sure you want to continue?
|
|
||||||
</p>
|
|
||||||
</Trans>
|
|
||||||
|
|
||||||
{chargeError && (
|
{chargeError && (
|
||||||
<div className="mt-4 flex text-red-500">
|
<div className="mt-4 flex text-red-500">
|
||||||
@@ -80,16 +71,7 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
|
|||||||
bookingId,
|
bookingId,
|
||||||
})
|
})
|
||||||
}>
|
}>
|
||||||
<Trans i18nKey="charge_card_confirm">
|
{t("charge_attendee", currencyStringParams)}
|
||||||
Charge attendee{" "}
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<FormattedNumber
|
|
||||||
value={props.paymentAmount / 100.0}
|
|
||||||
style="currency"
|
|
||||||
currency={props.paymentCurrency?.toUpperCase()}
|
|
||||||
/>
|
|
||||||
</IntlProvider>
|
|
||||||
</Trans>
|
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1784,6 +1784,10 @@
|
|||||||
"seats_and_no_show_fee_error": "Currently cannot enable seats and charge a no-show fee",
|
"seats_and_no_show_fee_error": "Currently cannot enable seats and charge a no-show fee",
|
||||||
"complete_your_booking": "Complete your booking",
|
"complete_your_booking": "Complete your booking",
|
||||||
"complete_your_booking_subject": "Complete your booking: {{title}} on {{date}}",
|
"complete_your_booking_subject": "Complete your booking: {{title}} on {{date}}",
|
||||||
|
"currency_string": "{{amount, currency}}",
|
||||||
|
"charge_card_dialog_body": "You are about to charge the attendee {{amount, currency}}. Are you sure you want to continue?",
|
||||||
|
"charge_attendee": "Charge attendee {{amount, currency}}",
|
||||||
|
"payment_app_commission": "Require payment ({{paymentFeePercentage}}% + {{fee, currency}} commission per transaction)",
|
||||||
"email_invite_team": "{{email}} has been invited",
|
"email_invite_team": "{{email}} has been invited",
|
||||||
"image_size_limit_exceed": "Uploaded image shouldn't exceed 5mb size limit"
|
"image_size_limit_exceed": "Uploaded image shouldn't exceed 5mb size limit"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { FormattedNumber, IntlProvider } from "react-intl";
|
|
||||||
|
|
||||||
import { useAppContextWithSchema } from "@calcom/app-store/EventTypeAppContext";
|
import { useAppContextWithSchema } from "@calcom/app-store/EventTypeAppContext";
|
||||||
import AppCard from "@calcom/app-store/_components/AppCard";
|
import AppCard from "@calcom/app-store/_components/AppCard";
|
||||||
@@ -47,11 +46,11 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
|||||||
description={
|
description={
|
||||||
<>
|
<>
|
||||||
<div className="">
|
<div className="">
|
||||||
{t("require_payment")} (0.5% +{" "}
|
{t("payment_app_commission", {
|
||||||
<IntlProvider locale="en">
|
paymentFeePercentage: 0.5,
|
||||||
<FormattedNumber value={0.1} style="currency" currency={currency} />
|
fee: 0.1,
|
||||||
</IntlProvider>{" "}
|
formatParams: { fee: { currency } },
|
||||||
{t("commission_per_transaction")})
|
})}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}>
|
}>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import classNames from "classnames";
|
|||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { FormattedNumber, IntlProvider } from "react-intl";
|
|
||||||
|
|
||||||
import { getSuccessPageLocationMessage } from "@calcom/app-store/locations";
|
import { getSuccessPageLocationMessage } from "@calcom/app-store/locations";
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
@@ -19,7 +18,7 @@ import type { PaymentPageProps } from "../pages/payment";
|
|||||||
import PaymentComponent from "./Payment";
|
import PaymentComponent from "./Payment";
|
||||||
|
|
||||||
const PaymentPage: FC<PaymentPageProps> = (props) => {
|
const PaymentPage: FC<PaymentPageProps> = (props) => {
|
||||||
const { t } = useLocale();
|
const { t, i18n } = useLocale();
|
||||||
const [is24h, setIs24h] = useState(isBrowserLocale24h());
|
const [is24h, setIs24h] = useState(isBrowserLocale24h());
|
||||||
const [date, setDate] = useState(dayjs.utc(props.booking.startTime));
|
const [date, setDate] = useState(dayjs.utc(props.booking.startTime));
|
||||||
const [timezone, setTimezone] = useState<string | null>(null);
|
const [timezone, setTimezone] = useState<string | null>(null);
|
||||||
@@ -107,13 +106,10 @@ const PaymentPage: FC<PaymentPageProps> = (props) => {
|
|||||||
{props.payment.paymentOption === "HOLD" ? t("no_show_fee") : t("price")}
|
{props.payment.paymentOption === "HOLD" ? t("no_show_fee") : t("price")}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-2 mb-6 font-semibold">
|
<div className="col-span-2 mb-6 font-semibold">
|
||||||
<IntlProvider locale="en">
|
{new Intl.NumberFormat(i18n.language, {
|
||||||
<FormattedNumber
|
style: "currency",
|
||||||
value={paymentAppData.price / 100.0}
|
currency: paymentAppData.currency,
|
||||||
style="currency"
|
}).format(paymentAppData.price / 100.0)}
|
||||||
currency={paymentAppData?.currency?.toUpperCase()}
|
|
||||||
/>
|
|
||||||
</IntlProvider>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import type { Prisma } from "@prisma/client";
|
import type { Prisma } from "@prisma/client";
|
||||||
import { SchedulingType } from "@prisma/client";
|
import { SchedulingType } from "@prisma/client";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { FormattedNumber, IntlProvider } from "react-intl";
|
|
||||||
import type { z } from "zod";
|
import type { z } from "zod";
|
||||||
|
|
||||||
import { classNames, parseRecurringEvent } from "@calcom/lib";
|
import { classNames, parseRecurringEvent } from "@calcom/lib";
|
||||||
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
|
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import type { baseEventTypeSelect } from "@calcom/prisma";
|
import type { baseEventTypeSelect } from "@calcom/prisma";
|
||||||
import type { EventTypeModel } from "@calcom/prisma/zod";
|
import type { EventTypeModel } from "@calcom/prisma/zod";
|
||||||
@@ -32,15 +30,13 @@ export const EventTypeDescription = ({
|
|||||||
shortenDescription,
|
shortenDescription,
|
||||||
isPublic,
|
isPublic,
|
||||||
}: EventTypeDescriptionProps) => {
|
}: EventTypeDescriptionProps) => {
|
||||||
const { t } = useLocale();
|
const { t, i18n } = useLocale();
|
||||||
|
|
||||||
const recurringEvent = useMemo(
|
const recurringEvent = useMemo(
|
||||||
() => parseRecurringEvent(eventType.recurringEvent),
|
() => parseRecurringEvent(eventType.recurringEvent),
|
||||||
[eventType.recurringEvent]
|
[eventType.recurringEvent]
|
||||||
);
|
);
|
||||||
|
|
||||||
const stripeAppData = getPaymentAppData(eventType);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={classNames("text-subtle", className)}>
|
<div className={classNames("text-subtle", className)}>
|
||||||
@@ -93,16 +89,13 @@ export const EventTypeDescription = ({
|
|||||||
</Badge>
|
</Badge>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
{stripeAppData.price > 0 && (
|
{eventType.price > 0 && (
|
||||||
<li>
|
<li>
|
||||||
<Badge variant="gray" startIcon={CreditCard}>
|
<Badge variant="gray" startIcon={CreditCard}>
|
||||||
<IntlProvider locale="en">
|
{new Intl.NumberFormat(i18n.language, {
|
||||||
<FormattedNumber
|
style: "currency",
|
||||||
value={stripeAppData.price / 100.0}
|
currency: eventType.currency,
|
||||||
style="currency"
|
}).format(eventType.price / 100)}
|
||||||
currency={stripeAppData?.currency?.toUpperCase()}
|
|
||||||
/>
|
|
||||||
</IntlProvider>
|
|
||||||
</Badge>
|
</Badge>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user