From e8fb326c09783fcb65fbb18a54f43e8e5d6898cb Mon Sep 17 00:00:00 2001 From: Ayush Rodrigues Date: Thu, 7 May 2026 12:31:27 +0100 Subject: [PATCH] add url to draft edit stripe --- shared/utils/productDisplayUtils.ts | 43 ++++++---- .../forms/shared/GenerateCheckoutStage.tsx | 70 +++++----------- .../forms/shared/SendInvoiceStage.tsx | 83 ++++++++----------- .../forms/shared/UrlSuccessView.tsx | 55 ++++++++++++ 4 files changed, 139 insertions(+), 112 deletions(-) create mode 100644 vite/src/components/forms/shared/UrlSuccessView.tsx diff --git a/shared/utils/productDisplayUtils.ts b/shared/utils/productDisplayUtils.ts index 9d11ac36d..1cba7d5a5 100644 --- a/shared/utils/productDisplayUtils.ts +++ b/shared/utils/productDisplayUtils.ts @@ -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, diff --git a/vite/src/components/forms/shared/GenerateCheckoutStage.tsx b/vite/src/components/forms/shared/GenerateCheckoutStage.tsx index 9e1145323..54f7188e7 100644 --- a/vite/src/components/forms/shared/GenerateCheckoutStage.tsx +++ b/vite/src/components/forms/shared/GenerateCheckoutStage.tsx @@ -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 ( - <> - - - -
-
- -
-

- The checkout URL has been generated and copied to your clipboard. -

-
-
- - - - - - + ); } diff --git a/vite/src/components/forms/shared/SendInvoiceStage.tsx b/vite/src/components/forms/shared/SendInvoiceStage.tsx index eacd96301..ba2a348fd 100644 --- a/vite/src/components/forms/shared/SendInvoiceStage.tsx +++ b/vite/src/components/forms/shared/SendInvoiceStage.tsx @@ -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( null, ); + const [completedDraftUrl, setCompletedDraftUrl] = useState( + 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 ( + + ); + } + if (completedInvoiceUrl) { return ( - <> - - - -
-
- -
-

- The invoice has been finalized and sent to the customer. -

-
-
- - - - - - + ); } diff --git a/vite/src/components/forms/shared/UrlSuccessView.tsx b/vite/src/components/forms/shared/UrlSuccessView.tsx new file mode 100644 index 000000000..30bd95d42 --- /dev/null +++ b/vite/src/components/forms/shared/UrlSuccessView.tsx @@ -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 ( + <> + + + +
+
+ +
+

{message}

+
+
+ + + + + + + ); +}