add url to draft edit stripe

This commit is contained in:
Ayush Rodrigues
2026-05-07 12:31:27 +01:00
parent a86f09105a
commit e8fb326c09
4 changed files with 139 additions and 112 deletions

View File

@@ -228,12 +228,20 @@ export const getFeaturePriceItemDisplay = ({
? `${numberWithCommas(includedUsage)} ${includedFeatureName}`
: "";
// Build price string (e.g., "$0.01")
const priceStr =
formatTiers({ item, currency, amountFormatOptions }) ??
(notNullish(item.price)
const volumeFlatAmount = isVolumeFlatAmountItem(item);
// For volume flat-amount items, tier.amount is always 0 — the real price
// lives in tier.flat_amount, so we must pass useFlatAmount: true.
let priceStr: string;
if (volumeFlatAmount) {
priceStr = formatTiers({ item, currency, amountFormatOptions, useFlatAmount: true }) ?? "";
} else if (item.tiers) {
priceStr = formatTiers({ item, currency, amountFormatOptions }) ?? "";
} else {
priceStr = notNullish(item.price)
? formatAmount({ currency, amount: item.price, amountFormatOptions })
: "");
: "";
}
// Build billing unit string (e.g., "credit" or "100 credits")
const billingUnits = item.billing_units ?? 1;
@@ -260,19 +268,12 @@ export const getFeaturePriceItemDisplay = ({
// Format output based on what we have
if (hasIncludedUsage) {
if (isVolumeFlatAmountItem(item)) {
const flatPriceStr =
formatTiers({
item,
currency,
amountFormatOptions,
useFlatAmount: true,
}) ?? "";
if (volumeFlatAmount) {
const featureName = getFeatureName({ feature, units: 2 });
return {
primary_text: includedUsageStr,
secondary_text:
`then ${flatPriceStr} for ${featureName} ${intervalStr}`.trim(),
`then ${priceStr} for ${featureName} ${intervalStr}`.trim(),
};
}
return {
@@ -282,6 +283,20 @@ export const getFeaturePriceItemDisplay = ({
};
}
if (volumeFlatAmount) {
const featureName = getFeatureName({ feature, units: 2 });
if (showInterval) {
return {
primary_text: priceStr,
secondary_text: `for ${featureName} ${intervalStr}`.trim(),
};
}
return {
primary_text: `${priceStr} for ${featureName}`.trim(),
secondary_text: undefined,
};
}
if (showInterval) {
return {
primary_text: priceStr,

View File

@@ -1,17 +1,16 @@
import { ArrowLeft, CheckCircleIcon, LinkIcon } from "@phosphor-icons/react";
import { ArrowLeft, LinkIcon } from "@phosphor-icons/react";
import { format } from "date-fns";
import { useMemo, useState } from "react";
import { toast } from "sonner";
import { Button } from "@/components/v2/buttons/Button";
import { CopyButton } from "@/components/v2/buttons/CopyButton";
import type { BillingLineItem } from "@/components/v2/LineItemsPreview";
import { LineItemsPreview } from "@/components/v2/LineItemsPreview";
import {
SheetFooter,
SheetHeader,
SheetSection,
} from "@/components/v2/sheets/SharedSheetComponents";
import { PlanActivationSection } from "./SendInvoiceStage";
import { UrlSuccessView } from "./UrlSuccessView";
export interface GenerateCheckoutSubmitParams {
enablePlanImmediately: boolean;
@@ -53,13 +52,13 @@ export function GenerateCheckoutStage({
const { paymentUrl } = await onSubmit({
enablePlanImmediately: enableImmediately,
});
if (paymentUrl) {
setCompletedCheckoutUrl(paymentUrl);
navigator.clipboard.writeText(paymentUrl);
toast.success("Checkout URL copied to clipboard");
} else {
toast.error("No checkout URL was returned. Please try again.");
}
if (paymentUrl) {
setCompletedCheckoutUrl(paymentUrl);
navigator.clipboard.writeText(paymentUrl);
toast.success("Checkout URL copied to clipboard");
} else {
toast.error("No checkout URL was returned. Please try again.");
}
} finally {
setIsSubmitting(false);
}
@@ -67,46 +66,17 @@ export function GenerateCheckoutStage({
if (completedCheckoutUrl) {
return (
<>
<SheetHeader
title="Checkout URL Generated"
description={
productName
? `Checkout session created for ${productName}`
: "Checkout session has been created"
}
noSeparator
/>
<SheetSection withSeparator={false}>
<div className="flex flex-col items-center gap-2 pt-4">
<div className="size-10 rounded-full bg-green-500/10 flex items-center justify-center">
<CheckCircleIcon
size={24}
weight="duotone"
className="text-green-500"
/>
</div>
<p className="text-sm text-t2 text-center">
The checkout URL has been generated and copied to your clipboard.
</p>
</div>
</SheetSection>
<SheetFooter className="flex flex-col grid-cols-1 mt-0">
<Button
variant="primary"
className="w-full"
onClick={() => window.open(completedCheckoutUrl, "_blank")}
>
Open checkout URL
</Button>
<CopyButton
text={completedCheckoutUrl}
innerClassName="text-xs text-t3 font-mono w-96"
/>
</SheetFooter>
</>
<UrlSuccessView
title="Checkout URL Generated"
description={
productName
? `Checkout session created for ${productName}`
: "Checkout session has been created"
}
message="The checkout URL has been generated and copied to your clipboard."
buttonLabel="Open checkout URL"
url={completedCheckoutUrl}
/>
);
}

View File

@@ -1,15 +1,9 @@
import type { AppEnv } from "@autumn/shared";
import {
ArrowLeft,
CheckCircleIcon,
HourglassIcon,
LightningIcon,
} from "@phosphor-icons/react";
import { ArrowLeft, HourglassIcon, LightningIcon } from "@phosphor-icons/react";
import { format } from "date-fns";
import { useCallback, useMemo, useState } from "react";
import { toast } from "sonner";
import { Button } from "@/components/v2/buttons/Button";
import { CopyButton } from "@/components/v2/buttons/CopyButton";
import { PanelButton } from "@/components/v2/buttons/PanelButton";
import { Input } from "@/components/v2/inputs/Input";
import type { BillingLineItem } from "@/components/v2/LineItemsPreview";
@@ -22,6 +16,7 @@ import {
import { useAxiosInstance } from "@/services/useAxiosInstance";
import { getStripeInvoiceLink } from "@/utils/linkUtils";
import { useCusQuery } from "@/views/customers/customer/hooks/useCusQuery";
import { UrlSuccessView } from "./UrlSuccessView";
export interface SendInvoiceSubmitParams {
enableProductImmediately: boolean;
@@ -117,6 +112,9 @@ export function SendInvoiceStage({
const [completedInvoiceUrl, setCompletedInvoiceUrl] = useState<string | null>(
null,
);
const [completedDraftUrl, setCompletedDraftUrl] = useState<string | null>(
null,
);
const [activeAction, setActiveAction] = useState<"draft" | "finalize" | null>(
null,
);
@@ -149,7 +147,9 @@ export function SendInvoiceStage({
finalizeInvoice: false,
});
if (stripeId) {
window.open(getInvoiceUrl(stripeId), "_blank");
const invoiceUrl = getInvoiceUrl(stripeId);
window.open(invoiceUrl, "_blank");
setCompletedDraftUrl(invoiceUrl);
}
} finally {
setActiveAction(null);
@@ -175,48 +175,35 @@ export function SendInvoiceStage({
const needsEmail = !customer?.email && !emailSaved;
if (completedDraftUrl) {
return (
<UrlSuccessView
title="Invoice Drafted"
description={
productName
? `Draft invoice for ${productName} created in Stripe`
: "Draft invoice created in Stripe"
}
message="Stripe should have opened in a new tab. If it was blocked, use the link below."
buttonLabel="Open in Stripe"
url={completedDraftUrl}
/>
);
}
if (completedInvoiceUrl) {
return (
<>
<SheetHeader
title="Invoice Sent"
description={
productName
? `Invoice for ${productName} has been finalized and sent`
: "Invoice has been finalized and sent"
}
noSeparator
/>
<SheetSection withSeparator={false}>
<div className="flex flex-col items-center gap-2 pt-4">
<div className="size-10 rounded-full bg-green-500/10 flex items-center justify-center">
<CheckCircleIcon
size={24}
weight="duotone"
className="text-green-500"
/>
</div>
<p className="text-sm text-t2 text-center">
The invoice has been finalized and sent to the customer.
</p>
</div>
</SheetSection>
<SheetFooter className="flex flex-col grid-cols-1 mt-0">
<Button
variant="primary"
className="w-full"
onClick={() => window.open(completedInvoiceUrl, "_blank")}
>
View Stripe invoice
</Button>
<CopyButton
text={completedInvoiceUrl}
innerClassName="text-xs text-t3 font-mono w-96"
/>
</SheetFooter>
</>
<UrlSuccessView
title="Invoice Sent"
description={
productName
? `Invoice for ${productName} has been finalized and sent`
: "Invoice has been finalized and sent"
}
message="The invoice has been finalized and sent to the customer."
buttonLabel="View Stripe invoice"
url={completedInvoiceUrl}
/>
);
}

View File

@@ -0,0 +1,55 @@
import { CheckCircleIcon } from "@phosphor-icons/react";
import { Button } from "@/components/v2/buttons/Button";
import { CopyButton } from "@/components/v2/buttons/CopyButton";
import {
SheetFooter,
SheetHeader,
SheetSection,
} from "@/components/v2/sheets/SharedSheetComponents";
export function UrlSuccessView({
title,
description,
message,
buttonLabel,
url,
}: {
title: string;
description: string;
message: string;
buttonLabel: string;
url: string;
}) {
return (
<>
<SheetHeader title={title} description={description} noSeparator />
<SheetSection withSeparator={false}>
<div className="flex flex-col items-center gap-2 pt-4">
<div className="size-10 rounded-full bg-green-500/10 flex items-center justify-center">
<CheckCircleIcon
size={24}
weight="duotone"
className="text-green-500"
/>
</div>
<p className="text-sm text-t2 text-center">{message}</p>
</div>
</SheetSection>
<SheetFooter className="flex flex-col grid-cols-1 mt-0">
<Button
variant="primary"
className="w-full"
onClick={() => window.open(url, "_blank")}
>
{buttonLabel}
</Button>
<CopyButton
text={url}
innerClassName="text-xs text-t3 font-mono w-96"
/>
</SheetFooter>
</>
);
}