-
Notifications
You must be signed in to change notification settings - Fork 10.5k
feat: Billing page redesign plus credits #23908
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 17 commits
f40c332
b10fb56
5f8243d
a244251
b719826
aa6dab5
32a013c
e5b9ab9
ee5a999
da4d6cc
47ccd0e
460237b
7586e87
ac0d846
a90a13b
22d9c56
a532d5d
a7806cf
0b24b48
b2a191a
2d8bcc8
76ad510
b69403c
6c2e24e
e8158d4
e9ea0b3
5f22b0c
65c8cfc
be6e62e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -75,23 +75,30 @@ const BillingView = () => { | |||||||||||||
} | ||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
// title={t("view_and_manage_billing_details")} | ||||||||||||||
// description={t("view_and_edit_billing_details")}> | ||||||||||||||
return ( | ||||||||||||||
<> | ||||||||||||||
<div className="border-subtle space-y-6 rounded-b-lg border border-t-0 px-6 py-8 text-sm sm:space-y-8"> | ||||||||||||||
<CtaRow title={t("view_and_manage_billing_details")} description={t("view_and_edit_billing_details")}> | ||||||||||||||
<Button color="primary" href={billingHref} target="_blank" EndIcon="external-link"> | ||||||||||||||
<div className="bg-muted border-muted mt-5 rounded-xl border p-1"> | ||||||||||||||
<div className="bg-default border-muted flex rounded-[10px] border px-5 py-4"> | ||||||||||||||
<div className="flex w-full flex-col gap-1"> | ||||||||||||||
<h3 className="text-emphasis text-sm font-semibold leading-none">{t("manage_billing")}</h3> | ||||||||||||||
<p className="text-subtle text-sm font-medium leading-tight"> | ||||||||||||||
{t("view_and_manage_billing_details")} | ||||||||||||||
</p> | ||||||||||||||
</div> | ||||||||||||||
<Button color="primary" href={billingHref} target="_blank" size="sm" EndIcon="external-link"> | ||||||||||||||
{t("billing_portal")} | ||||||||||||||
</Button> | ||||||||||||||
Comment on lines
+88
to
90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add rel="noopener noreferrer" to external link Prevents tabnabbing and drops referrer. - <Button color="primary" href={billingHref} target="_blank" size="sm" EndIcon="external-link">
+ <Button color="primary" href={billingHref} target="_blank" rel="noopener noreferrer" size="sm" EndIcon="external-link"> 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||
</CtaRow> | ||||||||||||||
</div> | ||||||||||||||
<BillingCredits /> | ||||||||||||||
<div className="border-subtle mt-6 space-y-6 rounded-lg border px-6 py-8 text-sm sm:space-y-8"> | ||||||||||||||
<CtaRow title={t("need_anything_else")} description={t("further_billing_help")}> | ||||||||||||||
<Button color="secondary" onClick={onContactSupportClick}> | ||||||||||||||
</div> | ||||||||||||||
<div className="flex items-center justify-between px-4 py-5"> | ||||||||||||||
<p className="text-subtle text-sm font-medium leading-tight">{t("need_help")}</p> | ||||||||||||||
<Button color="secondary" size="sm" onClick={onContactSupportClick}> | ||||||||||||||
{t("contact_support")} | ||||||||||||||
</Button> | ||||||||||||||
</CtaRow> | ||||||||||||||
</div> | ||||||||||||||
</div> | ||||||||||||||
<BillingCredits /> | ||||||||||||||
</> | ||||||||||||||
); | ||||||||||||||
}; | ||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -14,11 +14,14 @@ import { downloadAsCsv } from "@calcom/lib/csvUtils"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useLocale } from "@calcom/lib/hooks/useLocale"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useParamsWithFallback } from "@calcom/lib/hooks/useParamsWithFallback"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { trpc } from "@calcom/trpc/react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import classNames from "@calcom/ui/classNames"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { Button } from "@calcom/ui/components/button"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { Select } from "@calcom/ui/components/form"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { TextField, Label, InputError } from "@calcom/ui/components/form"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { Icon } from "@calcom/ui/components/icon"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { ProgressBar } from "@calcom/ui/components/progress-bar"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { showToast } from "@calcom/ui/components/toast"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { Tooltip } from "@calcom/ui/components/tooltip"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { BillingCreditsSkeleton } from "./BillingCreditsSkeleton"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -29,6 +32,39 @@ type MonthOption = { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
endDate: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type CreditRowProps = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
label: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
value: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
isBold?: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
underline?: "dashed" | "solid"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
className?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const CreditRow = ({ label, value, isBold = false, underline, className = "" }: CreditRowProps) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const numberFormatter = new Intl.NumberFormat(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
className={classNames( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
`my-1 flex justify-between`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
underline === "dashed" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
? "border-subtle border-b border-dashed" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
: underline === "solid" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
? "border-subtle border-b border-solid" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
: "mt-1", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
className | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
)}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<span | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
className={classNames("text-sm", isBold ? "font-semibold" : "text-subtle font-medium leading-tight")}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{label} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<span | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
className={classNames(`text-sm`, isBold ? "font-semibold" : "text-subtle font-medium leading-tight")}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{numberFormatter.format(value)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const getMonthOptions = (): MonthOption[] => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const options: MonthOption[] = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const minDate = dayjs.utc("2025-05-01"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -71,6 +107,7 @@ export default function BillingCredits() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const params = useParamsWithFallback(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const orgId = session.data?.user?.org?.id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const orgSlug = session.data?.user?.org?.slug; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const parsedTeamId = Number(params.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const teamId: number | undefined = Number.isFinite(parsedTeamId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -132,117 +169,150 @@ export default function BillingCredits() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
buyCreditsMutation.mutate({ quantity: data.quantity, teamId }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const teamCreditsPercentageUsed = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
creditsData.credits.totalMonthlyCredits > 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
? (creditsData.credits.totalRemainingMonthlyCredits / creditsData.credits.totalMonthlyCredits) * 100 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
: 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const totalCredits = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(creditsData.credits.totalCreditsForMonth ?? 0) || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
creditsData.credits.totalMonthlyCredits + creditsData.credits.additionalCredits; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const totalUsed = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(creditsData.credits.totalCreditsUsedThisMonth ?? 0) || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
totalCredits - (creditsData.credits.totalRemainingCreditsForMonth ?? 0); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const teamCreditsPercentageUsed = totalCredits > 0 ? (totalUsed / totalCredits) * 100 : 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const numberFormatter = new Intl.NumberFormat(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="border-subtle mt-8 space-y-6 rounded-lg border px-6 py-6 pb-6 text-sm sm:space-y-8"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<h2 className="text-base font-semibold">{t("credits")}</h2> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<ServerTrans | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
t={t} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
i18nKey="view_and_manage_credits_description" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
components={[ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Link | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
key="Credit System" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
className="underline underline-offset-2" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
target="_blank" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
href="https://cal.com/help/billing-and-usage/messaging-credits"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Learn more | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</Link>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
]} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="-mx-6 mt-6"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<hr className="border-subtle" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="bg-muted border-muted mt-5 rounded-xl border p-1"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="flex flex-col gap-1 px-4 py-5"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<h2 className="text-default text-base font-semibold leading-none">{t("credits")}</h2> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<p className="text-subtle text-sm font-medium leading-tight">{t("view_and_manage_credits")}</p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="mt-6"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{creditsData.credits.totalMonthlyCredits > 0 ? ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="mb-4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Label>{t("monthly_credits")}</Label> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<ProgressBar | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
color="green" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
percentageValue={teamCreditsPercentageUsed} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
label={`${Math.max(0, Math.round(teamCreditsPercentageUsed))}%`} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="text-subtle"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{t("total_credits", { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
totalCredits: creditsData.credits.totalMonthlyCredits, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
})} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="bg-default border-muted flex w-full rounded-[10px] border px-5 py-4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="w-full"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{totalCredits > 0 ? ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="mb-4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<CreditRow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
label={t("monthly_credits")} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
value={creditsData.credits.totalMonthlyCredits ?? 0} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
isBold={true} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
underline="dashed" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<CreditRow label={t("credits_used")} value={totalUsed} underline="solid" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<CreditRow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
label={t("total_credits_remaining")} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
value={creditsData.credits.totalRemainingCreditsForMonth} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="mt-4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<ProgressBar color="green" percentageValue={100 - teamCreditsPercentageUsed} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{t("remaining_credits", { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
remainingCredits: creditsData.credits.totalRemainingMonthlyCredits, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
})} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{/*750 credits per tip*/} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="mt-4 flex flex-1 items-center justify-between"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<p className="text-subtle text-sm font-medium leading-tight"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{orgSlug ? t("credits_per_tip_org") : t("credits_per_tip_teams")} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
href={ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
orgSlug | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
? `/settings/organizations/${orgSlug}/members` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
: `/settings/teams/${teamId}/members` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
size="sm" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
color="secondary"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{t("add_members_no_elipsis")} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</Button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) : ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<></> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="-mx-5 mt-5"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<hr className="border-subtle" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) : ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<></> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Label> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{creditsData.credits.totalMonthlyCredits ? t("additional_credits") : t("available_credits")} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</Label> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="mt-2 text-sm">{creditsData.credits.additionalCredits}</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="-mx-6 mb-6 mt-6"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<hr className="border-subtle mb-3 mt-3" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<form onSubmit={handleSubmit(onSubmit)} className="flex"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="-mb-1 mr-auto"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Label>{t("buy_additional_credits")}</Label> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="flex flex-col"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<TextField | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
required | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type="number" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{...register("quantity", { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
required: t("error_required_field"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
min: { value: 50, message: t("minimum_of_credits_required") }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
valueAsNumber: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
})} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
label="" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
containerClassName="w-60" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onChange={(e) => setValue("quantity", Number(e.target.value))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
min={50} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
addOnSuffix={<>{t("credits")}</>} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{/*Auto Top-Up goes here when we have it*/} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{/*<div className="-mx-5 mt-5"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<hr className="border-subtle" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div>*/} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{/*Additional Credits*/} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<form onSubmit={handleSubmit(onSubmit)} className="mt-4 flex"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="-mb-1 mr-auto w-full"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="flex justify-between"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="flex gap-1"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Label>{t("additional_credits")}</Label> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Tooltip content={t("view_additional_credits_expense_tip")}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Icon name="info" className="text-muted-foreground mt-0.5 h-3 w-3" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</Tooltip> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<p className="text-sm font-semibold leading-none"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{numberFormatter.format(creditsData.credits.additionalCredits)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="flex w-full items-center gap-2"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<TextField | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
required | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type="number" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{...register("quantity", { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
required: t("error_required_field"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
min: { value: 50, message: t("minimum_of_credits_required") }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
valueAsNumber: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
})} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
label="" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
containerClassName="w-full -mt-1" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
size="sm" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onChange={(e) => setValue("quantity", Number(e.target.value))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
min={50} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
addOnSuffix={<>{t("credits")}</>} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Button color="secondary" target="_blank" size="sm" type="submit" data-testid="buy-credits"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{t("buy")} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</Button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{errors.quantity && <InputError message={errors.quantity.message ?? t("invalid_input")} />} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</form> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="-mx-5 mt-5"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<hr className="border-subtle" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="mt-auto"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
color="primary" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
target="_blank" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
EndIcon="external-link" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type="submit" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
data-testid="buy-credits"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{t("buy_credits")} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</Button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</form> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="-mx-6 mb-6 mt-6"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<hr className="border-subtle mb-3 mt-3" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="flex"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="mr-auto"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Label className="mb-4">{t("download_expense_log")}</Label> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="mt-2 flex flex-col"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Select | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
options={monthOptions} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
value={selectedMonth} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onChange={(option) => option && setSelectedMonth(option)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{/*Download Expense Log*/} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="mt-4 flex"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="mr-auto w-full"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Label className="mb-4">{t("download_expense_log")}</Label> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="mr-2 mt-1"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Select | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
size="sm" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
className="w-full" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
innerClassNames={{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
control: "font-medium text-emphasis", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
options={monthOptions} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
value={selectedMonth} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onChange={(option) => option && setSelectedMonth(option)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="mt-auto"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Button onClick={handleDownload} loading={isDownloading} color="secondary" size="sm"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{t("download")} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</Button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="mt-auto"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Button onClick={handleDownload} loading={isDownloading} StartIcon="file-down"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{t("download")} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</Button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{/*Credit Worth Section*/} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="text-subtle px-5 py-4 text-sm font-medium leading-tight"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<ServerTrans | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
t={t} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
i18nKey="credit_worth_description" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
components={[ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Link | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
key="Credit System" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
className="underline underline-offset-2" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
target="_blank" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
href="https://cal.com/help/billing-and-usage/messaging-credits"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Learn more | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</Link>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
]} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+296
to
+308
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add rel for external link security and localize link label target="_blank" should include rel="noopener noreferrer". Also replace literal "Learn more" with a localized string. <ServerTrans
t={t}
i18nKey="credit_worth_description"
components={[
<Link
key="Credit System"
className="underline underline-offset-2"
- target="_blank"
- href="https://cal.com/help/billing-and-usage/messaging-credits">
- Learn more
+ target="_blank"
+ rel="noopener noreferrer"
+ href="https://cal.com/help/billing-and-usage/messaging-credits">
+ {t("learn_more")}
</Link>,
]}
/> 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
Uh oh!
There was an error while loading. Please reload this page.