diff --git a/vite/src/views/customers2/components/sheets/BalanceEditSheet.tsx b/vite/src/views/customers2/components/sheets/BalanceEditSheet.tsx
new file mode 100644
index 000000000..fa80540fc
--- /dev/null
+++ b/vite/src/views/customers2/components/sheets/BalanceEditSheet.tsx
@@ -0,0 +1,314 @@
+import {
+ type FullCusProduct,
+ type FullCustomerEntitlement,
+ type FullCustomerPrice,
+ getCusEntBalance,
+} from "@autumn/shared";
+import { ArrowLeft } from "@phosphor-icons/react";
+import { useEffect, useMemo, useState } from "react";
+import { toast } from "sonner";
+import { AdminHover } from "@/components/general/AdminHover";
+import { DateInputUnix } from "@/components/general/DateInputUnix";
+import { Button } from "@/components/v2/buttons/Button";
+import { CopyButton } from "@/components/v2/buttons/CopyButton";
+import { LabelInput } from "@/components/v2/inputs/LabelInput";
+import {
+ SheetFooter,
+ SheetHeader,
+ SheetSection,
+} from "@/components/v2/sheets/InlineSheet";
+import { useCustomerBalanceSheetStore } from "@/hooks/stores/useCustomerBalanceSheetStore";
+import { useSheetStore } from "@/hooks/stores/useSheetStore";
+import { CusService } from "@/services/customers/CusService";
+import { useAxiosInstance } from "@/services/useAxiosInstance";
+import { getBackendErr, notNullish } from "@/utils/genUtils";
+import { getCusEntHoverTexts } from "@/views/admin/adminUtils";
+import { useCusQuery } from "@/views/customers/customer/hooks/useCusQuery";
+import { InfoBox } from "@/views/onboarding2/integrate/components/InfoBox";
+import { useCustomerContext } from "../../customer/CustomerContext";
+
+export function BalanceEditSheet() {
+ const { customer, refetch } = useCusQuery();
+ const { entityId } = useCustomerContext();
+ const {
+ featureId,
+ originalEntitlements,
+ selectedCusEntId,
+ closeSheet: closeBalanceSheet,
+ } = useCustomerBalanceSheetStore();
+ const closeSheet = useSheetStore((s) => s.closeSheet);
+ const setSheet = useSheetStore((s) => s.setSheet);
+ const setBalanceSheet = useCustomerBalanceSheetStore((s) => s.setSheet);
+
+ const axiosInstance = useAxiosInstance();
+ const [updateLoading, setUpdateLoading] = useState(false);
+
+ const hasMultipleBalances = originalEntitlements.length > 1;
+
+ const initialFields = useMemo(() => {
+ if (!originalEntitlements.length) {
+ return new Map<
+ string,
+ { balance: number | null; next_reset_at: number | null }
+ >();
+ }
+
+ const fields = new Map<
+ string,
+ { balance: number | null; next_reset_at: number | null }
+ >();
+
+ for (const cusEnt of originalEntitlements) {
+ const balance = getCusEntBalance({
+ cusEnt,
+ entityId,
+ }).balance;
+
+ fields.set(cusEnt.id, {
+ balance,
+ next_reset_at: cusEnt.next_reset_at,
+ });
+ }
+
+ return fields;
+ }, [originalEntitlements, entityId]);
+
+ const [updateFields, setUpdateFields] = useState(initialFields);
+
+ // Reset fields when feature changes
+ useEffect(() => {
+ setUpdateFields(initialFields);
+ }, [initialFields]);
+
+ const getCusProduct = (cusEnt: FullCustomerEntitlement) => {
+ const cusProduct = customer?.customer_products.find(
+ (cp: FullCusProduct) => cp.id === cusEnt.customer_product_id,
+ );
+ return cusProduct;
+ };
+
+ const handleClose = () => {
+ closeBalanceSheet();
+ closeSheet();
+ };
+
+ const handleBackToSelection = () => {
+ setBalanceSheet({
+ type: "edit-balance",
+ featureId,
+ originalEntitlements,
+ selectedCusEntId: null,
+ });
+ setSheet({ type: "balance-selection" });
+ };
+
+ const handleUpdateCusEntitlement = async (
+ cusEnt: FullCustomerEntitlement,
+ ) => {
+ const fields = updateFields.get(cusEnt.id);
+ if (!fields) return;
+
+ const balanceInt = parseFloat(String(fields.balance));
+ if (Number.isNaN(balanceInt)) {
+ toast.error("Balance not valid");
+ return;
+ }
+
+ const cusProduct = getCusProduct(cusEnt);
+ const cusPrice = cusProduct?.customer_prices.find(
+ (cp: FullCustomerPrice) =>
+ cp.price.entitlement_id === cusEnt.entitlement.id,
+ );
+
+ if (cusPrice && fields.next_reset_at !== cusEnt.next_reset_at) {
+ toast.error("Not allowed to change reset at for paid features");
+ return;
+ }
+
+ setUpdateLoading(true);
+ try {
+ await CusService.updateCusEntitlement(
+ axiosInstance,
+ customer.id || customer.internal_id,
+ cusEnt.id,
+ {
+ balance: balanceInt,
+ next_reset_at: fields.next_reset_at,
+ entity_id: entityId,
+ },
+ );
+ toast.success("Balance updated successfully");
+ await refetch();
+ handleClose();
+ } catch (error) {
+ toast.error(getBackendErr(error, "Failed to update entitlement"));
+ }
+ setUpdateLoading(false);
+ };
+
+ if (!featureId || !originalEntitlements.length) {
+ return (
+
+
+
+ );
+ }
+
+ const firstEnt = originalEntitlements[0];
+ const feature = firstEnt.entitlement.feature;
+
+ // Get the selected entitlement
+ const selectedCusEnt = hasMultipleBalances
+ ? originalEntitlements.find((ent) => ent.id === selectedCusEntId)
+ : originalEntitlements[0];
+
+ if (!selectedCusEnt) {
+ return (
+
+
+
+ );
+ }
+
+ const fields = updateFields.get(selectedCusEnt.id);
+ if (!fields) return null;
+
+ const cusProduct = getCusProduct(selectedCusEnt);
+ const cusPrice = cusProduct?.customer_prices.find(
+ (cp: FullCustomerPrice) =>
+ cp.price.entitlement_id === selectedCusEnt.entitlement.id,
+ );
+
+ return (
+
+
+ {feature.id}
+
+ }
+ >
+ {hasMultipleBalances && (
+
+ )}
+
+
+
+
+
+
+
+ Plan ID:
+
+
+ {cusProduct?.product_id || "N/A"}
+
+
+ {cusProduct?.entity_id && (
+
+ Entity ID:
+
+ {cusProduct.entity_id}
+
+
+ )}
+
+
+ Reset Interval:
+
+
+ {selectedCusEnt.entitlement.interval === "lifetime"
+ ? "never"
+ : selectedCusEnt.entitlement.interval}
+
+
+
+
+
+
+
+
+
{
+ const newFields = new Map(updateFields);
+ const current = newFields.get(selectedCusEnt.id) || {
+ balance: null,
+ next_reset_at: null,
+ };
+ newFields.set(selectedCusEnt.id, {
+ ...current,
+ balance: e.target.value ? parseFloat(e.target.value) : null,
+ });
+ setUpdateFields(newFields);
+ }}
+ />
+
+
+
Next Reset
+
{
+ const newFields = new Map(updateFields);
+ const current = newFields.get(selectedCusEnt.id) || {
+ balance: null,
+ next_reset_at: null,
+ };
+ newFields.set(selectedCusEnt.id, {
+ ...current,
+ next_reset_at: unixDate,
+ });
+ setUpdateFields(newFields);
+ }}
+ />
+
+
+
+ {cusPrice && (
+
+ Reset cycle cannot be changed for paid features, as it follows
+ the billing cycle.
+
+ )}
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/vite/src/views/customers2/components/sheets/BalanceSelectionSheet.tsx b/vite/src/views/customers2/components/sheets/BalanceSelectionSheet.tsx
new file mode 100644
index 000000000..126f7c5a3
--- /dev/null
+++ b/vite/src/views/customers2/components/sheets/BalanceSelectionSheet.tsx
@@ -0,0 +1,120 @@
+import {
+ type FullCusProduct,
+ type FullCustomerEntitlement,
+ getCusEntBalance,
+} from "@autumn/shared";
+import { CopyButton } from "@/components/v2/buttons/CopyButton";
+import { SheetHeader, SheetSection } from "@/components/v2/sheets/InlineSheet";
+import { useCustomerBalanceSheetStore } from "@/hooks/stores/useCustomerBalanceSheetStore";
+import { useSheetStore } from "@/hooks/stores/useSheetStore";
+import { notNullish } from "@/utils/genUtils";
+import { useCusQuery } from "@/views/customers/customer/hooks/useCusQuery";
+import { useCustomerContext } from "../../customer/CustomerContext";
+
+export function BalanceSelectionSheet() {
+ const { customer } = useCusQuery();
+ const { entityId } = useCustomerContext();
+ const {
+ featureId,
+ originalEntitlements,
+ setSheet: setBalanceSheet,
+ } = useCustomerBalanceSheetStore();
+ const setSheet = useSheetStore((s) => s.setSheet);
+
+ if (!featureId || !originalEntitlements.length) {
+ return (
+
+
+
+ );
+ }
+
+ const firstEnt = originalEntitlements[0];
+ const feature = firstEnt.entitlement.feature;
+
+ const getCusProduct = (cusEnt: FullCustomerEntitlement) => {
+ const cusProduct = customer?.customer_products.find(
+ (cp: FullCusProduct) => cp.id === cusEnt.customer_product_id,
+ );
+ return cusProduct;
+ };
+
+ const handleSelectBalance = (cusEntId: string) => {
+ setBalanceSheet({
+ type: "edit-balance",
+ featureId,
+ originalEntitlements,
+ selectedCusEntId: cusEntId,
+ });
+ setSheet({ type: "balance-edit" });
+ };
+
+ return (
+
+
+ {feature.name}
+
+ }
+ />
+
+
+
+
+ {originalEntitlements.map((cusEnt: FullCustomerEntitlement) => {
+ const cusProduct = getCusProduct(cusEnt);
+ const balance = getCusEntBalance({
+ cusEnt,
+ entityId,
+ }).balance;
+
+ return (
+
+ );
+ })}
+
+
+
+
+ );
+}
diff --git a/vite/src/views/customers2/components/sheets/SubscriptionDetailSheet.tsx b/vite/src/views/customers2/components/sheets/SubscriptionDetailSheet.tsx
index caec9f0fb..764b36275 100644
--- a/vite/src/views/customers2/components/sheets/SubscriptionDetailSheet.tsx
+++ b/vite/src/views/customers2/components/sheets/SubscriptionDetailSheet.tsx
@@ -1,4 +1,5 @@
import {
+ CusProductStatus,
type Entity,
type FeatureOptions,
getProductItemDisplay,
@@ -8,10 +9,13 @@ import {
Calendar,
CheckCircle,
CreditCard,
+ CubeIcon,
+ GitBranchIcon,
Hash,
+ HashIcon,
Info,
- Package,
PencilSimple,
+ PencilSimpleIcon,
Tag,
XCircle,
} from "@phosphor-icons/react";
@@ -20,12 +24,12 @@ import { useEffect } from "react";
import { useNavigate } from "react-router";
// import { Badge } from "@/components/v2/Badge";
import { Button } from "@/components/v2/buttons/Button";
+import { IconButton } from "@/components/v2/buttons/IconButton";
import { SheetHeader, SheetSection } from "@/components/v2/sheets/InlineSheet";
import { SheetFooter } from "@/components/v2/sheets/SharedSheetComponents";
import { useOrg } from "@/hooks/common/useOrg";
import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
import {
- useHasChanges,
usePrepaidItems,
useProductStore,
} from "@/hooks/stores/useProductStore";
@@ -47,14 +51,18 @@ export function SubscriptionDetailSheet() {
const resetProductStore = useProductStore((s) => s.reset);
const sheetType = useSheetStore((s) => s.type);
// Get edited product from store
- const hasChanges = useHasChanges();
+
const storeProduct = useProductStore((s) => s.product);
// Check if there are changes in the product store
- const shouldShowEditedProduct = hasChanges && !!storeProduct;
+ const showUpdateProduct = storeProduct?.id;
// Get customer product and productV2 by itemId
const { cusProduct, productV2 } = useSubscriptionById({ itemId });
+ const isExpired = cusProduct?.status === CusProductStatus.Expired;
+
+ console.log("cusProduct", cusProduct);
+ console.log("productV2", productV2);
useEffect(() => {
if (
@@ -120,42 +128,67 @@ export function SubscriptionDetailSheet() {
return (
{/* Product Information */}
-
-
-
}
- label="Product Name"
- value={cusProduct.product.name}
- />
-
}
- label="Product ID"
- value={cusProduct.product_id}
- mono
- />
-
}
- label="Version"
- value={cusProduct.product.version}
- />
- {cusProduct.quantity && cusProduct.quantity > 1 && (
+
}
+ >
+ Edit Plan
+
+ )
+ }
+ >
+
+
}
- label="Quantity"
- value={cusProduct.quantity.toString()}
+ icon={}
+ label="Plan"
+ value={cusProduct.product.name}
/>
+ }
+ label="ID"
+ value={cusProduct.product_id}
+ mono
+ />
+ }
+ label="Version"
+ value={cusProduct.product.version}
+ />
+ {cusProduct.quantity && cusProduct.quantity > 1 && (
+ }
+ label="Quantity"
+ value={cusProduct.quantity.toString()}
+ />
+ )}
+
+ {!isExpired && (
+
}
+ >
+ Edit Plan
+
)}
{/* Status & Dates */}
-
+
}
@@ -257,72 +290,70 @@ export function SubscriptionDetailSheet() {
)}
{/* Edited Plan Items - Show pending changes */}
- {shouldShowEditedProduct &&
- storeProduct &&
- storeProduct.items.length > 0 && (
-
-
-
-
-
- These changes are pending and will be applied when you save.
-
-
- {storeProduct.items.map((item, index) => {
- const display = getProductItemDisplay({
- item,
- features,
- currency: org?.default_currency || "USD",
- fullDisplay: true,
- amountFormatOptions: { currencyDisplay: "narrowSymbol" },
- });
+ {showUpdateProduct && storeProduct && storeProduct.items.length > 0 && (
+
+
+
+
+
+ These changes are pending and will be applied when you save.
+
+
+ {storeProduct.items.map((item, index) => {
+ const display = getProductItemDisplay({
+ item,
+ features,
+ currency: org?.default_currency || "USD",
+ fullDisplay: true,
+ amountFormatOptions: { currencyDisplay: "narrowSymbol" },
+ });
- const isFeatureItem =
- item.type === ProductItemType.Feature ||
- item.type === ProductItemType.FeaturePrice;
+ const isFeatureItem =
+ item.type === ProductItemType.Feature ||
+ item.type === ProductItemType.FeaturePrice;
- // Find prepaid quantity from cusProduct.options
- const prepaidOption = cusProduct?.options?.find(
- (opt: FeatureOptions) => opt.feature_id === item.feature_id,
- );
- const prepaidQuantity = prepaidOption
- ? prepaidOption.quantity / (item.billing_units || 1)
- : null;
+ // Find prepaid quantity from cusProduct.options
+ const prepaidOption = cusProduct?.options?.find(
+ (opt: FeatureOptions) => opt.feature_id === item.feature_id,
+ );
+ const prepaidQuantity = prepaidOption
+ ? prepaidOption.quantity / (item.billing_units || 1)
+ : null;
- return (
-
-
-
-
- {display.primary_text}
- {prepaidQuantity !== null && (
-
- Qty: {prepaidQuantity}
-
- )}
-
- {display.secondary_text && (
-
- {display.secondary_text}
-
+ return (
+
+
+
+
+ {display.primary_text}
+ {prepaidQuantity !== null && (
+
+ Qty: {prepaidQuantity}
+
)}
+ {display.secondary_text && (
+
+ {display.secondary_text}
+
+ )}
- );
- })}
-
-
- )}
+
+ );
+ })}
+
+
+ )}
{/* Pricing Summary */}
@@ -389,34 +420,21 @@ export function SubscriptionDetailSheet() {
)}
-
- {shouldShowEditedProduct ? (
- <>
-
+ )}
);
}
@@ -431,10 +449,12 @@ interface InfoRowProps {
function InfoRow({ icon, label, value, className, mono }: InfoRowProps) {
return (
-
-
{icon}
-
-
{label}
+
+
{icon}
+
+
+ {label}
+
;
}) => {
const { customer } = useCusQuery();
const customerId = customer?.id;
@@ -39,6 +37,7 @@ const FormContent = ({
const prepaidItems = usePrepaidItems({ product });
const prepaidOptions = form.state.values.prepaidOptions;
+ const initialPrepaidOptions = form.state.values.initialPrepaidOptions;
const previewQuery = useAttachPreview({
customerId,
@@ -61,7 +60,8 @@ const FormContent = ({
// Check if there are any changes from initial values
const hasQuantityChanges = prepaidItems.some((item) => {
const currentQuantity = prepaidOptions?.[item.feature_id as string];
- const initialQuantity = initialPrepaidOptions[item.feature_id as string];
+ const initialQuantity =
+ initialPrepaidOptions?.[item.feature_id as string];
return currentQuantity !== initialQuantity;
});
@@ -76,6 +76,7 @@ const FormContent = ({
previewData={previewQuery.data}
isLoading={previewQuery.isLoading}
product={product}
+ form={form}
/>
s.setSheet);
const storeProduct = useProductStore((s) => s.product);
- const form = useAttachProductForm({
- initialProductId: cusProduct?.product.id ?? undefined,
- });
-
// Memoize initial prepaid options from cusProduct
const initialPrepaidOptions = useMemo(
() =>
@@ -119,6 +116,11 @@ function SheetContent({
[cusProduct.options],
);
+ const form = useAttachProductForm({
+ initialProductId: cusProduct?.product.id ?? undefined,
+ initialPrepaidOptions,
+ });
+
const product = storeProduct?.id ? storeProduct : (productV2 ?? undefined);
const prepaidItems = usePrepaidItems({ product });
@@ -183,7 +185,6 @@ function SheetContent({
productV2={productV2}
cusProduct={cusProduct}
form={form}
- initialPrepaidOptions={initialPrepaidOptions}
/>
>
)}
@@ -200,7 +201,6 @@ export function SubscriptionUpdateSheet() {
const { cusProduct, productV2 } = useSubscriptionById({ itemId });
- const entityId = cusProduct?.entity_id ?? undefined;
const sheetType = useSheetStore((s) => s.type);
const resetProductStore = useProductStore((s) => s.reset);
diff --git a/vite/src/views/customers2/components/table/customer-balance/CustomerBalanceTable.tsx b/vite/src/views/customers2/components/table/customer-balance/CustomerBalanceTable.tsx
index 328d2e2c6..5a491d676 100644
--- a/vite/src/views/customers2/components/table/customer-balance/CustomerBalanceTable.tsx
+++ b/vite/src/views/customers2/components/table/customer-balance/CustomerBalanceTable.tsx
@@ -4,6 +4,7 @@ import type {
} from "@autumn/shared";
import { Table } from "@/components/general/table";
import { useCustomerBalanceSheetStore } from "@/hooks/stores/useCustomerBalanceSheetStore";
+import { useSheetStore } from "@/hooks/stores/useSheetStore";
import { useCustomerTable } from "@/views/customers2/hooks/useCustomerTable";
import { CustomerBalanceTableColumns } from "./CustomerBalanceTableColumns";
@@ -20,7 +21,15 @@ export function CustomerBalanceTable({
aggregatedMap: Map;
isLoading: boolean;
}) {
- const setSheet = useCustomerBalanceSheetStore((s) => s.setSheet);
+ const setBalanceSheet = useCustomerBalanceSheetStore((s) => s.setSheet);
+ const setSheet = useSheetStore((s) => s.setSheet);
+ const sheetType = useSheetStore((s) => s.type);
+ const balanceOpen =
+ sheetType === "balance-selection" || sheetType === "balance-edit";
+ const selectedCusEntId = useCustomerBalanceSheetStore(
+ (s) => s.selectedCusEntId,
+ );
+ const selectedFeatureId = useCustomerBalanceSheetStore((s) => s.featureId);
const columns = CustomerBalanceTableColumns({
filteredCustomerProducts,
@@ -38,11 +47,37 @@ export function CustomerBalanceTable({
const handleRowClick = (ent: FullCusEntWithFullCusProduct) => {
const featureId = ent.entitlement.feature.id;
const ents = aggregatedMap.get(featureId) || [ent];
- setSheet({
+ const hasMultipleBalances = ents.length > 1;
+
+ // Set balance data in balance store
+ setBalanceSheet({
type: "edit-balance",
featureId,
originalEntitlements: ents,
+ selectedCusEntId: hasMultipleBalances ? null : ents[0].id,
});
+
+ // Open the appropriate inline sheet
+ if (hasMultipleBalances) {
+ setSheet({ type: "balance-selection" });
+ } else {
+ setSheet({ type: "balance-edit" });
+ }
+ };
+
+ // Determine the selected row ID based on whether it's an aggregated balance or single balance
+ const getSelectedRowId = () => {
+ if (!balanceOpen) return undefined;
+ // For single balance selection, match by customer entitlement ID
+ if (selectedCusEntId) return selectedCusEntId;
+ // For aggregated balance selection, find the row that matches the feature ID
+ if (selectedFeatureId) {
+ const matchingEnt = allEnts.find(
+ (ent) => ent.entitlement.feature.id === selectedFeatureId,
+ );
+ return matchingEnt?.id;
+ }
+ return undefined;
};
return (
@@ -53,6 +88,8 @@ export function CustomerBalanceTable({
enableSorting,
isLoading,
onRowClick: handleRowClick,
+ flexibleTableColumns: true,
+ selectedItemId: getSelectedRowId(), //decides the highlighted row on sheetopen
}}
>
diff --git a/vite/src/views/customers2/components/table/customer-balance/CustomerBalanceTableColumns.tsx b/vite/src/views/customers2/components/table/customer-balance/CustomerBalanceTableColumns.tsx
index 3fd24e8b3..058bcbf27 100644
--- a/vite/src/views/customers2/components/table/customer-balance/CustomerBalanceTableColumns.tsx
+++ b/vite/src/views/customers2/components/table/customer-balance/CustomerBalanceTableColumns.tsx
@@ -24,8 +24,9 @@ export const CustomerBalanceTableColumns = ({
}) => [
{
header: "Feature",
- size: 160,
accessorKey: "feature",
+ enableResizing: true,
+ minSize: 100,
cell: ({ row }: { row: Row }) => {
const ent = row.original;
const featureId = ent.entitlement.feature.id;
@@ -49,7 +50,9 @@ export const CustomerBalanceTableColumns = ({
},
{
header: "Usage",
- size: 200,
+ // enableResizing: true,
+ // size: 200,
+ // minSize: 100,
accessorKey: "usage",
cell: ({ row }: { row: Row }) => {
const ent = row.original;
@@ -136,29 +139,31 @@ export const CustomerBalanceTableColumns = ({
);
},
},
- {
- header: "Reset Date",
- size: 120,
- accessorKey: "reset_date",
- cell: ({ row }: { row: Row }) => {
- const ent = row.original;
+ // {
+ // header: "Reset Date",
+ // size: 120,
+ // accessorKey: "reset_date",
+ // cell: ({ row }: { row: Row }) => {
+ // const ent = row.original;
- if (!ent.next_reset_at) {
- return ;
- }
+ // if (!ent.next_reset_at) {
+ // return ;
+ // }
- return (
-
-
- Resets {formatUnixToDateTimeString(ent.next_reset_at)}
-
-
- );
- },
- },
+ // return (
+ //
+ //
+ // Resets {formatUnixToDateTimeString(ent.next_reset_at)}
+ //
+ //
+ // );
+ // },
+ // },
{
header: "Bar",
size: 220,
+ // maxSize: 220,
+ // enableResizing: true,
accessorKey: "bar",
cell: ({ row }: { row: Row }) => {
const ent = row.original;
@@ -193,12 +198,17 @@ export const CustomerBalanceTableColumns = ({
return (
- {/*
+
Resets {formatUnixToDateTimeString(ent.next_reset_at)}
- */}
+
0 ? "opacity-100" : "opacity-0",
)}
>
diff --git a/vite/src/views/customers2/components/table/customer-invoices/CustomerInvoicesTable.tsx b/vite/src/views/customers2/components/table/customer-invoices/CustomerInvoicesTable.tsx
index 7209c3983..26f2b3083 100644
--- a/vite/src/views/customers2/components/table/customer-invoices/CustomerInvoicesTable.tsx
+++ b/vite/src/views/customers2/components/table/customer-invoices/CustomerInvoicesTable.tsx
@@ -78,7 +78,7 @@ export function CustomerInvoicesTable() {
},
});
- const hasInvoices = invoices.length > 0;
+ // const hasInvoices = invoices.length > 0;
return (
diff --git a/vite/src/views/customers2/components/table/customer-list/CustomerListTable.tsx b/vite/src/views/customers2/components/table/customer-list/CustomerListTable.tsx
index 1afb6d6d2..eac20d0cf 100644
--- a/vite/src/views/customers2/components/table/customer-list/CustomerListTable.tsx
+++ b/vite/src/views/customers2/components/table/customer-list/CustomerListTable.tsx
@@ -21,6 +21,8 @@ export function CustomerListTable({
}) {
const navigate = useNavigate();
+ // Close any open sheet on mount in useEffect
+
const columns = useMemo(() => createCustomerListColumns(), []);
const table = useCustomerTable({
diff --git a/vite/src/views/customers2/components/table/customer-products/AttachProductSheetTrigger.tsx b/vite/src/views/customers2/components/table/customer-products/AttachProductSheetTrigger.tsx
index fd942d650..ca48fd297 100644
--- a/vite/src/views/customers2/components/table/customer-products/AttachProductSheetTrigger.tsx
+++ b/vite/src/views/customers2/components/table/customer-products/AttachProductSheetTrigger.tsx
@@ -6,27 +6,28 @@ import {
useSheetStore,
} from "@/hooks/stores/useSheetStore";
import { useEntity } from "@/hooks/stores/useSubscriptionStore";
+import { cn } from "@/lib/utils";
export function AttachProductSheetTrigger() {
const { setSheet, closeSheet } = useSheetStore();
const isAttachingProduct = useIsAttachingProduct();
const { entity } = useEntity();
const features = useFeaturesQuery();
+ const sheetType = useSheetStore((s) => s.type);
const feature = features.features.find((f) => f.id === entity?.feature_id);
const handleClick = () => {
- if (isAttachingProduct) {
- closeSheet();
- } else {
- setSheet({ type: "attach-product" });
- }
+ setSheet({ type: "attach-product" });
};
return (
diff --git a/vite/src/views/customers2/components/table/customer-products/CustomerProductsColumns.tsx b/vite/src/views/customers2/components/table/customer-products/CustomerProductsColumns.tsx
index 57a35253f..f5a7fbb4d 100644
--- a/vite/src/views/customers2/components/table/customer-products/CustomerProductsColumns.tsx
+++ b/vite/src/views/customers2/components/table/customer-products/CustomerProductsColumns.tsx
@@ -18,7 +18,7 @@ export const CustomerProductsColumns = [
const showQuantity = quantity && quantity > 1;
return (
-
+
{row.original.product.name}
diff --git a/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx b/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx
index a32a456a7..32f4468b8 100644
--- a/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx
+++ b/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx
@@ -34,6 +34,9 @@ export function CustomerProductsTable() {
const [selectedProduct, setSelectedProduct] = useState
(
null,
);
+ const sheetType = useSheetStore((s) => s.type);
+ const selectedItemId = useSheetStore((s) => s.itemId);
+ const detailsOpen = sheetType === "subscription-detail";
const { setEntityId } = useEntity();
@@ -227,6 +230,8 @@ export function CustomerProductsTable() {
isLoading,
onRowClick: handleRowClick,
emptyStateText,
+ flexibleTableColumns: true,
+ selectedItemId: detailsOpen ? selectedItemId : undefined,
}}
>
@@ -281,6 +286,8 @@ export function CustomerProductsTable() {
isLoading,
onRowClick: handleRowClick,
emptyStateText: "No entity-level plans found",
+ flexibleTableColumns: true,
+ selectedItemId: detailsOpen ? selectedItemId : undefined,
}}
>
diff --git a/vite/src/views/customers2/components/table/customer-usage-analytics/CustomerUsageAnalyticsChart.tsx b/vite/src/views/customers2/components/table/customer-usage-analytics/CustomerUsageAnalyticsChart.tsx
index 9bbd35f1f..43a795a6a 100644
--- a/vite/src/views/customers2/components/table/customer-usage-analytics/CustomerUsageAnalyticsChart.tsx
+++ b/vite/src/views/customers2/components/table/customer-usage-analytics/CustomerUsageAnalyticsChart.tsx
@@ -8,6 +8,7 @@ import {
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
+import { useIsSheetOpen } from "@/hooks/stores/useSheetStore";
import {
prepareChartData,
prepareTimeseriesChartData,
@@ -23,6 +24,7 @@ export function CustomerUsageAnalyticsChart({
events?: Event[];
daysToShow?: number;
}) {
+ const isSheetOpen = useIsSheetOpen();
function formatYAxisTick(value: number): string {
// if (value === 0) return "";
@@ -59,7 +61,7 @@ export function CustomerUsageAnalyticsChart({
return (
}) => {
return (
@@ -18,7 +17,6 @@ export const CustomerUsageAnalyticsColumns = [
{
header: "Value",
accessorKey: "value",
- size: 60,
cell: ({ row }: { row: Row
}) => {
const event = row.original;
return (
@@ -44,14 +42,13 @@ export const CustomerUsageAnalyticsColumns = [
{
header: "Timestamp",
accessorKey: "timestamp",
- size: 100,
cell: ({ row }: { row: Row }) => {
// type is Date but actually comes as a string
const dateObj = new Date(row.original.timestamp as unknown as string);
const dateAsNumber = dateObj.getTime();
return (
-
+
{/* {formatUnixToDateTimeWithMs(dateAsNumber)} */}
{format(new Date(dateAsNumber), "d MMM HH:mm:ss")}
diff --git a/vite/src/views/customers2/components/table/customer-usage-analytics/CustomerUsageAnalyticsTable.tsx b/vite/src/views/customers2/components/table/customer-usage-analytics/CustomerUsageAnalyticsTable.tsx
index a41578cd4..d103bfe21 100644
--- a/vite/src/views/customers2/components/table/customer-usage-analytics/CustomerUsageAnalyticsTable.tsx
+++ b/vite/src/views/customers2/components/table/customer-usage-analytics/CustomerUsageAnalyticsTable.tsx
@@ -2,7 +2,6 @@ import { ChartBar } from "@phosphor-icons/react";
import { parseAsInteger, useQueryState } from "nuqs";
import { useMemo } from "react";
import { Table } from "@/components/general/table";
-import { cn } from "@/lib/utils";
import { useCusEventsQuery } from "@/views/customers/customer/hooks/useCusEventsQuery";
import { useCusQuery } from "@/views/customers/customer/hooks/useCusQuery";
import { useCustomerTable } from "@/views/customers2/hooks/useCustomerTable";
@@ -93,7 +92,8 @@ export function CustomerUsageAnalyticsTable() {
numberOfColumns: CustomerUsageAnalyticsColumns.length,
enableSorting,
isLoading,
- rowClassName: "h-8 bg-interactive-secondary",
+ rowClassName: "h-8 bg-interactive-secondary dark:bg-card",
+ flexibleTableColumns: true,
}}
>
@@ -130,26 +130,15 @@ export function CustomerUsageAnalyticsTable() {
) : hasEvents ? (
<>
-
-
-
-
+
-
+
s.closeSheet);
//Close the subscription detail / attach product sheet when navigating to this page (prevents jank closing animation)
@@ -52,68 +49,12 @@ export default function CustomerProductView() {
const { isLoading: orgLoading } = useOrg();
const { isLoading: featuresLoading } = useFeaturesQuery();
- const [options, setOptions] = useState([]);
- const [entityId, setEntityId] = useState(entityIdParam);
- const [entityFeatureIds, setEntityFeatureIds] = useState([]);
-
- const {
- product: originalProduct,
- cusProduct,
- isLoading,
- error,
- } = useCusProductQuery();
+ const { product: originalProduct, isLoading, error } = useCusProductQuery();
useProductSync({ product: originalProduct });
const { isLoading: cusLoading } = useCusQuery();
- //probs not needed anymore? used to pass entityId into the ProductContext
- //now we can get it from CusProductQuery?
- // useEffect(() => {
- // if (entityIdParam) {
- // setEntityId(entityIdParam);
- // } else {
- // setEntityId(null);
- // }
- // }, [entityIdParam]);
-
- // useEffect(() => {
- // if (!originalProduct) return;
-
- // const product = originalProduct;
-
- // console.log("[CPV] effect", {
- // prodId: originalProduct.id,
- // v: originalProduct.version,
- // cusId: cusProduct?.id,
- // });
-
- // // Update initialProductRef BEFORE setProduct to ensure useAttachState
- // // effect has the correct baseline when it runs
- // initialProductRef.current = structuredClone({
- // ...product,
- // items: sortProductItems(product.items),
- // });
-
- // setProduct(product);
-
- // setEntityFeatureIds(
- // Array.from(
- // new Set(
- // product.items
- // .filter((item: ProductItem) => notNullish(item.entity_feature_id))
- // .map((item: ProductItem) => item.entity_feature_id!),
- // ),
- // ),
- // );
-
- // if (cusProduct?.options) {
- // setOptions(cusProduct.options);
- // } else {
- // setOptions([]);
- // }
- // }, [originalProduct, cusProduct]);
-
if (error) {
return (
@@ -132,23 +73,11 @@ export default function CustomerProductView() {
}
return (
-
+ <>
-
+ >
);
}
diff --git a/vite/src/views/customers2/customer/CustomerBalanceSheets.tsx b/vite/src/views/customers2/customer/CustomerBalanceSheets.tsx
deleted file mode 100644
index e9289b872..000000000
--- a/vite/src/views/customers2/customer/CustomerBalanceSheets.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { CustomerBalanceModal } from "./components/CustomerBalanceModal";
-
-export const CustomerBalanceSheets = () => {
- return ;
-};
diff --git a/vite/src/views/customers2/customer/CustomerBreadcrumbs2.tsx b/vite/src/views/customers2/customer/CustomerBreadcrumbs2.tsx
index 728ee73c2..ae018018e 100644
--- a/vite/src/views/customers2/customer/CustomerBreadcrumbs2.tsx
+++ b/vite/src/views/customers2/customer/CustomerBreadcrumbs2.tsx
@@ -53,7 +53,7 @@ export const CustomerBreadcrumbs = () => {
-
+
{entityId ? (
{
{entityId && (
<>
-
+
{entity?.name || entityId}
>
diff --git a/vite/src/views/customers2/customer/CustomerSheets.tsx b/vite/src/views/customers2/customer/CustomerSheets.tsx
index 4d039251a..27d5fa654 100644
--- a/vite/src/views/customers2/customer/CustomerSheets.tsx
+++ b/vite/src/views/customers2/customer/CustomerSheets.tsx
@@ -1,9 +1,11 @@
import { AnimatePresence, motion } from "motion/react";
-import { createPortal } from "react-dom";
import { SheetContainer } from "@/components/v2/sheets/InlineSheet";
import { SheetCloseButton } from "@/components/v2/sheets/SheetCloseButton";
+import { useCustomerBalanceSheetStore } from "@/hooks/stores/useCustomerBalanceSheetStore";
import { useSheetStore } from "@/hooks/stores/useSheetStore";
import { AttachProductSheet } from "../components/sheets/AttachProductSheet";
+import { BalanceEditSheet } from "../components/sheets/BalanceEditSheet";
+import { BalanceSelectionSheet } from "../components/sheets/BalanceSelectionSheet";
import { SubscriptionDetailSheet } from "../components/sheets/SubscriptionDetailSheet";
import { SubscriptionUpdateSheet } from "../components/sheets/SubscriptionUpdateSheet";
import { SHEET_ANIMATION } from "./customerAnimations";
@@ -11,6 +13,12 @@ import { SHEET_ANIMATION } from "./customerAnimations";
export function CustomerSheets() {
const sheetType = useSheetStore((s) => s.type);
const closeSheet = useSheetStore((s) => s.closeSheet);
+ const closeBalanceSheet = useCustomerBalanceSheetStore((s) => s.closeSheet);
+
+ const handleClose = () => {
+ closeSheet();
+ closeBalanceSheet();
+ };
const renderSheet = () => {
switch (sheetType) {
@@ -20,12 +28,16 @@ export function CustomerSheets() {
return ;
case "subscription-update":
return ;
+ case "balance-selection":
+ return ;
+ case "balance-edit":
+ return ;
default:
return null;
}
};
- return createPortal(
+ return (
{sheetType && (
-
-
+
+
{renderSheet()}
)}
- ,
- document.body,
+
);
// return (
diff --git a/vite/src/views/customers2/customer/CustomerView2.tsx b/vite/src/views/customers2/customer/CustomerView2.tsx
index ba10f4404..972cbee80 100644
--- a/vite/src/views/customers2/customer/CustomerView2.tsx
+++ b/vite/src/views/customers2/customer/CustomerView2.tsx
@@ -1,10 +1,8 @@
"use client";
import { AnimatePresence, motion } from "motion/react";
-import { useEffect } from "react";
import { createPortal } from "react-dom";
import { Link } from "react-router";
-import { useCustomerBalanceSheetStore } from "@/hooks/stores/useCustomerBalanceSheetStore";
import { useHasChanges } from "@/hooks/stores/useProductStore";
import { useSheetStore } from "@/hooks/stores/useSheetStore";
import { useEntity } from "@/hooks/stores/useSubscriptionStore";
@@ -18,7 +16,6 @@ import { CustomerInvoicesTable } from "../components/table/customer-invoices/Cus
import { CustomerProductsTable } from "../components/table/customer-products/CustomerProductsTable";
import { CustomerUsageAnalyticsTable } from "../components/table/customer-usage-analytics/CustomerUsageAnalyticsTable";
import { CustomerActions } from "./CustomerActions";
-import { CustomerBalanceSheets } from "./CustomerBalanceSheets";
import { CustomerBreadcrumbs } from "./CustomerBreadcrumbs2";
import { CustomerContext } from "./CustomerContext";
import { CustomerPageDetails } from "./CustomerPageDetails";
@@ -32,22 +29,11 @@ export default function CustomerView2() {
useCusReferralQuery();
const { entityId, setEntityId } = useEntity();
- const closeSheet = useCustomerBalanceSheetStore((s) => s.closeSheet);
const sheetType = useSheetStore((s) => s.type);
const closeProductSheet = useSheetStore((s) => s.closeSheet);
const hasChanges = useHasChanges();
- // Close modal on mount
- useEffect(() => {
- closeSheet();
- }, [closeSheet]);
-
- // Clear selected entity on unmount (when navigating away)
- // useEffect(() => {
- // return () => {
- // setEntityId(null);
- // };
- // }, [setEntityId]);
+ // useSheetCleanup();
if (cusLoading) return ;
@@ -104,7 +90,7 @@ export default function CustomerView2() {
{/*
*/}
{/*
*/}
-
+
{/* */}
@@ -121,7 +107,7 @@ export default function CustomerView2() {
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
- className="fixed inset-0 bg-background/60"
+ className="fixed inset-0 bg-white/60 dark:bg-black/60"
style={{ zIndex: 40 }}
onMouseDown={() => {
!hasChanges && closeProductSheet();
@@ -133,7 +119,6 @@ export default function CustomerView2() {
)}
-
diff --git a/vite/src/views/customers2/customer/components/CustomerBalanceModal.tsx b/vite/src/views/customers2/customer/components/CustomerBalanceModal.tsx
deleted file mode 100644
index c05d68197..000000000
--- a/vite/src/views/customers2/customer/components/CustomerBalanceModal.tsx
+++ /dev/null
@@ -1,376 +0,0 @@
-import {
- type FullCusProduct,
- type FullCustomerEntitlement,
- type FullCustomerPrice,
- getCusEntBalance,
-} from "@autumn/shared";
-import { useEffect, useMemo, useState } from "react";
-import { toast } from "sonner";
-import { DateInputUnix } from "@/components/general/DateInputUnix";
-import { Button } from "@/components/v2/buttons/Button";
-import { CopyButton } from "@/components/v2/buttons/CopyButton";
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
-} from "@/components/v2/dialogs/Dialog";
-import { LabelInput } from "@/components/v2/inputs/LabelInput";
-import { useCustomerBalanceSheetStore } from "@/hooks/stores/useCustomerBalanceSheetStore";
-import { CusService } from "@/services/customers/CusService";
-import { useAxiosInstance } from "@/services/useAxiosInstance";
-import { getBackendErr, notNullish } from "@/utils/genUtils";
-import { useCusQuery } from "@/views/customers/customer/hooks/useCusQuery";
-import { InfoBox } from "@/views/onboarding2/integrate/components/InfoBox";
-import { AdminHover } from "../../../../components/general/AdminHover";
-import { getCusEntHoverTexts } from "../../../admin/adminUtils";
-import { useCustomerContext } from "../CustomerContext";
-
-export function CustomerBalanceModal() {
- const { customer, refetch } = useCusQuery();
- const { entityId } = useCustomerContext();
- const { type, featureId, originalEntitlements, closeSheet } =
- useCustomerBalanceSheetStore();
-
- const [updateLoading, setUpdateLoading] = useState
(null);
- const [selectedCusEntId, setSelectedCusEntId] = useState(null);
- const axiosInstance = useAxiosInstance();
-
- const initialFields = useMemo(() => {
- if (!originalEntitlements.length) {
- return new Map<
- string,
- { balance: number | null; next_reset_at: number | null }
- >();
- }
-
- const fields = new Map<
- string,
- { balance: number | null; next_reset_at: number | null }
- >();
-
- for (const cusEnt of originalEntitlements) {
- const balance = getCusEntBalance({
- cusEnt,
- entityId,
- }).balance;
-
- fields.set(cusEnt.id, {
- balance,
- next_reset_at: cusEnt.next_reset_at,
- });
- }
-
- return fields;
- }, [featureId, entityId, originalEntitlements]);
-
- const [updateFields, setUpdateFields] = useState(initialFields);
-
- // Update fields when featureId changes (reset state for new balance)
- useEffect(() => {
- setUpdateFields(initialFields);
- setSelectedCusEntId(null);
- }, [initialFields]);
-
- if (!featureId || !originalEntitlements.length) return null;
-
- const firstEnt = originalEntitlements[0];
- const feature = firstEnt.entitlement.feature;
- const hasMultipleBalances = originalEntitlements.length > 1;
- const showSelectionStep = hasMultipleBalances && !selectedCusEntId;
-
- const getCusProduct = (cusEnt: FullCustomerEntitlement) => {
- const cusProduct = customer.customer_products.find(
- (cp: FullCusProduct) => cp.id === cusEnt.customer_product_id,
- );
- return cusProduct;
- };
-
- const handleUpdateCusEntitlement = async (
- cusEnt: FullCustomerEntitlement,
- ) => {
- const fields = updateFields.get(cusEnt.id);
- if (!fields) return;
-
- const balanceInt = parseFloat(String(fields.balance));
- if (Number.isNaN(balanceInt)) {
- toast.error("Balance not valid");
- return;
- }
-
- const cusProduct = getCusProduct(cusEnt);
- const cusPrice = cusProduct?.customer_prices.find(
- (cp: FullCustomerPrice) =>
- cp.price.entitlement_id === cusEnt.entitlement.id,
- );
-
- if (cusPrice && fields.next_reset_at !== cusEnt.next_reset_at) {
- toast.error(`Not allowed to change reset at for paid features`);
- return;
- }
-
- setUpdateLoading(cusEnt.id);
- try {
- await CusService.updateCusEntitlement(
- axiosInstance,
- customer.id || customer.internal_id,
- cusEnt.id,
- {
- balance: balanceInt,
- next_reset_at: fields.next_reset_at,
- entity_id: entityId,
- },
- );
- toast.success("Balance updated successfully");
- await refetch();
- closeSheet();
- } catch (error) {
- toast.error(getBackendErr(error, "Failed to update entitlement"));
- }
- setUpdateLoading(null);
- };
-
- // Render selection step
- if (showSelectionStep) {
- return (
-
- );
- }
-
- // Render update form step
- const selectedCusEnt = hasMultipleBalances
- ? originalEntitlements.find((ent) => ent.id === selectedCusEntId)
- : originalEntitlements[0];
-
- if (!selectedCusEnt) return null;
-
- return (
-
- );
-}
diff --git a/vite/src/views/developer/configure-stripe/ConfigureStripe.tsx b/vite/src/views/developer/configure-stripe/ConfigureStripe.tsx
index ed426458c..3d770689d 100644
--- a/vite/src/views/developer/configure-stripe/ConfigureStripe.tsx
+++ b/vite/src/views/developer/configure-stripe/ConfigureStripe.tsx
@@ -231,7 +231,7 @@ export const ConfigureStripe = () => {
{" "}
Visit the Stripe dashboard{" "}
-
+ {
placeholder="eg. https://useautumn.com"
className={urlError ? "border-red-500" : ""}
/>
- {urlError && (
- {urlError}
- )}
+ {urlError && {urlError}
}
@@ -379,4 +377,4 @@ export const ConfigureStripe = () => {
);
-};
\ No newline at end of file
+};
diff --git a/vite/src/views/products/plan/ProductSheets.tsx b/vite/src/views/products/plan/ProductSheets.tsx
index 72c05caf9..ed231ae48 100644
--- a/vite/src/views/products/plan/ProductSheets.tsx
+++ b/vite/src/views/products/plan/ProductSheets.tsx
@@ -1,6 +1,5 @@
import { type ProductItem, productV2ToFeatureItems } from "@autumn/shared";
import { AnimatePresence, motion } from "motion/react";
-import { createPortal } from "react-dom";
import { SheetContainer } from "@/components/v2/sheets/InlineSheet";
import { SheetCloseButton } from "@/components/v2/sheets/SheetCloseButton";
import { useProductStore } from "@/hooks/stores/useProductStore";
@@ -80,7 +79,7 @@ export const ProductSheets = () => {
}
};
- return createPortal(
+ return (
{sheetType && (
{
animate={{ x: 0 }}
exit={{ x: "100%" }}
transition={SHEET_ANIMATION}
- className="fixed right-0 top-0 bottom-0"
+ className="absolute right-0 top-0 bottom-0"
style={{ width: "28rem", zIndex: 100 }}
>
-
+
{renderSheet()}
)}
- ,
- document.body,
+
);
};
diff --git a/vite/src/views/products/plan/components/EditPlanHeader.tsx b/vite/src/views/products/plan/components/EditPlanHeader.tsx
index 0cb6e08a7..8be8aca61 100644
--- a/vite/src/views/products/plan/components/EditPlanHeader.tsx
+++ b/vite/src/views/products/plan/components/EditPlanHeader.tsx
@@ -1,4 +1,5 @@
import { UserIcon } from "@phosphor-icons/react";
+import { parseAsString, useQueryStates } from "nuqs";
import { useState } from "react";
import { toast } from "sonner";
import { AdminHover } from "@/components/general/AdminHover";
@@ -17,10 +18,11 @@ import {
useIsCusPlanEditor,
useProductStore,
} from "@/hooks/stores/useProductStore.ts";
-import { useSheetStore } from "@/hooks/stores/useSheetStore";
import { useAxiosInstance } from "@/services/useAxiosInstance";
import { getBackendErr } from "@/utils/genUtils";
import { isOneOffProduct } from "@/utils/product/priceUtils";
+import { useCusQuery } from "@/views/customers/customer/hooks/useCusQuery.tsx";
+import { useCusProductQuery } from "@/views/customers/customer/product/hooks/useCusProductQuery.tsx";
import { useMigrationsQuery } from "../../product/hooks/queries/useMigrationsQuery.tsx.tsx";
import { useProductCountsQuery } from "../../product/hooks/queries/useProductCountsQuery";
import {
@@ -39,7 +41,6 @@ export const EditPlanHeader = () => {
const { refetch: refetchMigrations } = useMigrationsQuery();
const { queryStates, setQueryStates } = useProductQueryState();
const axiosInstance = useAxiosInstance();
- const sheetType = useSheetStore((s) => s.type);
const isCusPlanEditor = useIsCusPlanEditor();
const [confirmMigrateOpen, setConfirmMigrateOpen] = useState(false);
@@ -116,19 +117,24 @@ export const EditPlanHeader = () => {
version={version}
/>
-
+ {isCusPlanEditor ? (
+
+ ) : (
+
+ )}
+
@@ -171,7 +177,7 @@ export const EditPlanHeader = () => {
value={currentVersion.toString()}
onValueChange={handleVersionChange}
>
-
+
@@ -190,3 +196,41 @@ export const EditPlanHeader = () => {
>
);
};
+
+const CustomerBreadcrumbs = () => {
+ const { customer } = useCusQuery();
+ const { product } = useCusProductQuery();
+ const [{ entity_id }] = useQueryStates({
+ entity_id: parseAsString,
+ });
+ //find entity name
+ const entity = customer.entities.find((e: any) => e.id === entity_id);
+
+ return (
+
+ );
+};
diff --git a/vite/src/views/products/plan/components/PlanEditor.tsx b/vite/src/views/products/plan/components/PlanEditor.tsx
index 6eb62ee6c..f7fa6c017 100644
--- a/vite/src/views/products/plan/components/PlanEditor.tsx
+++ b/vite/src/views/products/plan/components/PlanEditor.tsx
@@ -81,15 +81,7 @@ export const PlanEditor = () => {
e.stopPropagation()}>
- {/* */}
- {
- // if (shouldCloseSheetOnMouseDown({ e, item, sheetType })) {
- // closeSheet();
- // }
- // }}
- >
+
{useIsCusPlanEditor() &&
}
@@ -108,7 +100,7 @@ export const PlanEditor = () => {
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
- className="fixed inset-0 bg-background/70"
+ className="fixed inset-0 bg-white/70 dark:bg-black/70"
style={{ zIndex: 40 }}
onMouseDown={(e) => {
if (shouldCloseSheetOnMouseDown({ e, item, sheetType })) {
diff --git a/vite/src/views/products/plan/components/edit-plan-feature/BillingType.tsx b/vite/src/views/products/plan/components/edit-plan-feature/BillingType.tsx
index 8b9a375f2..c4723458a 100644
--- a/vite/src/views/products/plan/components/edit-plan-feature/BillingType.tsx
+++ b/vite/src/views/products/plan/components/edit-plan-feature/BillingType.tsx
@@ -12,16 +12,12 @@ import { CoinsIcon } from "@phosphor-icons/react";
import { PanelButton } from "@/components/v2/buttons/PanelButton";
import { IncludedUsageIcon } from "@/components/v2/icons/AutumnIcons";
import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { useProductStore } from "@/hooks/stores/useProductStore";
import { useProductItemContext } from "@/views/products/product/product-item/ProductItemContext";
export function BillingType() {
const { features } = useFeaturesQuery();
const { item, setItem } = useProductItemContext();
- const product = useProductStore((s) => s.product);
- const setProduct = useProductStore((s) => s.setProduct);
-
if (!item) return null;
// Derive billing type from item state
@@ -97,7 +93,7 @@ export function BillingType() {
{
setBillingType("included");
}}
@@ -124,7 +120,7 @@ export function BillingType() {
icon={}
/>
-
Paid
+
Priced
{isConsumable
? `Charge a price for usage of this feature (e.g. $0.05 per ${singleFeatureName}).`
diff --git a/vite/src/views/products/plan/components/edit-plan-feature/EditPlanFeatureSheet.tsx b/vite/src/views/products/plan/components/edit-plan-feature/EditPlanFeatureSheet.tsx
index 4d78a4839..f46ae69b7 100644
--- a/vite/src/views/products/plan/components/edit-plan-feature/EditPlanFeatureSheet.tsx
+++ b/vite/src/views/products/plan/components/edit-plan-feature/EditPlanFeatureSheet.tsx
@@ -52,25 +52,21 @@ export function EditPlanFeatureSheet({
- {hasChosenBillingType && (
- <>
-
-
-
+
+
+
- {isFeaturePrice && (
-
-
-
-
-
- )}
-
-
- >
+ {isFeaturePrice && (
+
+
+
+
+
)}
+
+
>
)}
diff --git a/vite/src/views/products/plan/components/new-feature/NewFeatureDetails.tsx b/vite/src/views/products/plan/components/new-feature/NewFeatureDetails.tsx
index b05c6dacb..dcf6e7831 100644
--- a/vite/src/views/products/plan/components/new-feature/NewFeatureDetails.tsx
+++ b/vite/src/views/products/plan/components/new-feature/NewFeatureDetails.tsx
@@ -33,7 +33,7 @@ export function NewFeatureDetails({
Name
setSource(e.target.value)}
/>
@@ -42,7 +42,7 @@ export function NewFeatureDetails({
ID
setTarget(e.target.value)}
/>
diff --git a/vite/src/views/products/plan/components/plan-card/PlanCard.tsx b/vite/src/views/products/plan/components/plan-card/PlanCard.tsx
index d41da6f38..c2dd235e8 100644
--- a/vite/src/views/products/plan/components/plan-card/PlanCard.tsx
+++ b/vite/src/views/products/plan/components/plan-card/PlanCard.tsx
@@ -22,7 +22,7 @@ export default function sPlanCard() {
>
{/* Overlay when sheet is open that lets you hover on plan card buttons */}
{sheetType && (
-
+
)}