-
{t("total_credits", { totalCredits: creditsData.teamCredits.totalMonthlyCredits })}
-
- {t("remaining_credits", {
- remainingCredits: creditsData.teamCredits.totalRemainingMonthlyCredits,
- })}
+ {creditsData.credits.totalMonthlyCredits > 0 ? (
+
+
+
+
+
+ {t("total_credits", {
+ totalCredits: creditsData.credits.totalMonthlyCredits,
+ })}
+
+
+ {t("remaining_credits", {
+ remainingCredits: creditsData.credits.totalRemainingMonthlyCredits,
+ })}
+
-
-
-
{creditsData.teamCredits.additionalCredits}
{" "}
+ ) : (
+ <>>
+ )}
+
+
+
{creditsData.credits.additionalCredits}
diff --git a/apps/web/pages/api/twilio/webhook.ts b/apps/web/pages/api/twilio/webhook.ts
index c0149bdebd..93cf993b77 100644
--- a/apps/web/pages/api/twilio/webhook.ts
+++ b/apps/web/pages/api/twilio/webhook.ts
@@ -92,15 +92,15 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
teamIdToCharge = teamMembership?.teamId;
}
- if (teamIdToCharge) {
- await creditService.chargeCredits({
- teamId: teamIdToCharge,
- bookingUid: parsedBookingUid,
- smsSid,
- credits: 0,
- });
- return res.status(200).send(`SMS to US and CA are free on a team plan. Credits set to 0`);
- }
+ await creditService.chargeCredits({
+ teamId: teamIdToCharge,
+ userId: !teamIdToCharge ? parsedUserId : undefined,
+ bookingUid: parsedBookingUid,
+ smsSid,
+ credits: 0,
+ });
+
+ return res.status(200).send(`SMS to US and CA are free on a team plan. Credits set to 0`);
}
let orgId;
@@ -140,7 +140,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
const credits = price ? creditService.calculateCreditsFromPrice(price) : null;
- const chargedTeamId = await creditService.chargeCredits({
+ const chargedUserOrTeamId = await creditService.chargeCredits({
credits,
teamId: parsedTeamId,
userId: parsedUserId,
@@ -148,10 +148,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
bookingUid: parsedBookingUid,
});
- if (chargedTeamId) {
+ if (chargedUserOrTeamId) {
return res.status(200).send(
`Expense log with ${credits ? credits : "no"} credits created for
- teamId ${chargedTeamId}`
+ ${
+ chargedUserOrTeamId.teamId
+ ? `teamId ${chargedUserOrTeamId.teamId}`
+ : `userId ${chargedUserOrTeamId.userId}`
+ }`
);
}
// this should never happen - even when out of credits we still charge a team
diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json
index 6f90b5204b..41dbdf6a94 100644
--- a/apps/web/public/static/locales/en/common.json
+++ b/apps/web/public/static/locales/en/common.json
@@ -3195,9 +3195,13 @@
"view_form": "View Form",
"sms_opt_out_message": "Text STOP to opt-out of SMS messages",
"team_credits_low_warning": "Your team {{teamName}} is running low on credits",
+ "user_credits_low_warning": "You are running low on credits",
"action_required_out_of_credits": "[Action Required] Your team {{teamName}} has run out of credits",
+ "action_required_user_out_of_credits": "[Action Required] You have run out of credits",
"low_credits_warning_message": "Your Cal.com team {{teamName}} is running low on credits. To avoid any disruption in service, please purchase additional credits. If your balance runs out, SMS messages will stop sending and will be sent as emails instead.",
+ "low_credits_warning_message_user": "Your Cal.com account is running low on credits. To avoid any disruption in service, please purchase additional credits. If your balance runs out, SMS messages will stop sending and will be sent as emails instead.",
"credit_limit_reached_message": "Your Cal.com team {{teamName}} has run out of credits. As a result, SMS messages are now being sent via email instead. To resume sending SMS, please purchase additional credits.",
+ "credit_limit_reached_message_user": "Your Cal.com account has run out of credits. As a result, SMS messages are now being sent via email instead. To resume sending SMS, please purchase additional credits.",
"current_credit_balance": "Current balance: {{balance}} credits",
"notification_about_your_booking": "Notification about your booking",
"monthly_credits": "Monthly credits",
@@ -3211,5 +3215,8 @@
"matching": "Matching",
"event_redirect": "Event Redirect",
"reset_form": "Reset Form",
+ "requires_credits": "Requires credits",
+ "requires_credits_tooltip": "You need enough credits in your account to use this feature",
+ "available_credits": "Available credits",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
diff --git a/packages/emails/email-manager.ts b/packages/emails/email-manager.ts
index 4c2b3f5025..41cdc07556 100644
--- a/packages/emails/email-manager.ts
+++ b/packages/emails/email-manager.ts
@@ -762,47 +762,75 @@ export const sendBookingRedirectNotification = async (bookingRedirect: IBookingR
};
export const sendCreditBalanceLowWarningEmails = async (input: {
- team: {
- name: string;
+ team?: {
+ name: string | null;
id: number;
adminAndOwners: {
- name: string;
+ id: number;
+ name: string | null;
email: string;
t: TFunction;
}[];
};
+ user?: {
+ id: number;
+ name: string | null;
+ email: string;
+ t: TFunction;
+ };
balance: number;
}) => {
- const { team, balance } = input;
- if (!team.adminAndOwners.length) return;
- const emailsToSend: Promise
[] = [];
+ const { team, balance, user } = input;
+ if ((!team || !team.adminAndOwners.length) && !user) return;
- for (const admin of team.adminAndOwners) {
- emailsToSend.push(sendEmail(() => new CreditBalanceLowWarningEmail(admin, balance, team)));
+ if (team) {
+ const emailsToSend: Promise[] = [];
+
+ for (const admin of team.adminAndOwners) {
+ emailsToSend.push(sendEmail(() => new CreditBalanceLowWarningEmail({ user: admin, balance, team })));
+ }
+
+ await Promise.all(emailsToSend);
}
- await Promise.all(emailsToSend);
+ if (user) {
+ await sendEmail(() => new CreditBalanceLowWarningEmail({ user, balance }));
+ }
};
export const sendCreditBalanceLimitReachedEmails = async ({
team,
+ user,
}: {
- team: {
+ team?: {
name: string;
id: number;
adminAndOwners: {
- name: string;
+ id: number;
+ name: string | null;
email: string;
t: TFunction;
}[];
};
+ user?: {
+ id: number;
+ name: string | null;
+ email: string;
+ t: TFunction;
+ };
}) => {
- if (!team.adminAndOwners.length) return;
- const emailsToSend: Promise[] = [];
+ if ((!team || !team.adminAndOwners.length) && !user) return;
- for (const admin of team.adminAndOwners) {
- emailsToSend.push(sendEmail(() => new CreditBalanceLimitReachedEmail(admin, team)));
+ if (team) {
+ const emailsToSend: Promise[] = [];
+
+ for (const admin of team.adminAndOwners) {
+ emailsToSend.push(sendEmail(() => new CreditBalanceLimitReachedEmail({ user: admin, team })));
+ }
+ await Promise.all(emailsToSend);
}
- await Promise.all(emailsToSend);
+ if (user) {
+ await sendEmail(() => new CreditBalanceLimitReachedEmail({ user }));
+ }
};
diff --git a/packages/emails/src/templates/CreditBalanceLimitReachedEmail.tsx b/packages/emails/src/templates/CreditBalanceLimitReachedEmail.tsx
index 562636a088..5c48a99fad 100644
--- a/packages/emails/src/templates/CreditBalanceLimitReachedEmail.tsx
+++ b/packages/emails/src/templates/CreditBalanceLimitReachedEmail.tsx
@@ -7,11 +7,12 @@ import type { BaseScheduledEmail } from "./BaseScheduledEmail";
export const CreditBalanceLimitReachedEmail = (
props: {
- team: {
+ team?: {
id: number;
name: string;
};
user: {
+ id: number;
name: string;
email: string;
t: TFunction;
@@ -20,21 +21,41 @@ export const CreditBalanceLimitReachedEmail = (
) => {
const { team, user } = props;
+ if (team) {
+ return (
+
+
+ <> {user.t("hi_user_name", { name: user.name })},>
+
+
+ <>{user.t("credit_limit_reached_message", { teamName: team.name })}>
+
+
+
+
{" "}
+
+ );
+ }
+
return (
-
+
<> {user.t("hi_user_name", { name: user.name })},>
- <>{user.t("credit_limit_reached_message", { teamName: team.name })}>
+ <>{user.t("credit_limit_reached_message_user")}>
-
{" "}
+
);
};
diff --git a/packages/emails/src/templates/CreditBalanceLowWarningEmail.tsx b/packages/emails/src/templates/CreditBalanceLowWarningEmail.tsx
index cb70d56e24..0a6a26abe2 100644
--- a/packages/emails/src/templates/CreditBalanceLowWarningEmail.tsx
+++ b/packages/emails/src/templates/CreditBalanceLowWarningEmail.tsx
@@ -7,12 +7,13 @@ import type { BaseScheduledEmail } from "./BaseScheduledEmail";
export const CreditBalanceLowWarningEmail = (
props: {
- team: {
+ team?: {
id: number;
name: string;
};
balance: number;
user: {
+ id: number;
name: string;
email: string;
t: TFunction;
@@ -21,26 +22,46 @@ export const CreditBalanceLowWarningEmail = (
) => {
const { team, balance, user } = props;
+ if (team) {
+ return (
+