From c90d14eb50e938c4c677bfad79669faae0d61240 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Thu, 16 Oct 2025 10:36:52 +0100 Subject: [PATCH 1/2] fix: restore product delete logic and improve onboarding UX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Restore product delete logic to check productCounts for Archive/Delete text - Make onboarding plan card base price clickable (with disabled state) - Adjust pricing table breakpoints for better responsive wrapping - Fix base price button opacity when disabled 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../autumn/pricing-table-preview.tsx | 2 +- .../views/onboarding3/OnboardingPreview.tsx | 45 +++++++------------ .../product-row-toolbar/ProductRowToolbar.tsx | 37 ++++++++++++--- 3 files changed, 46 insertions(+), 38 deletions(-) diff --git a/vite/src/components/autumn/pricing-table-preview.tsx b/vite/src/components/autumn/pricing-table-preview.tsx index 9cd26bedf..a2c9f2c17 100644 --- a/vite/src/components/autumn/pricing-table-preview.tsx +++ b/vite/src/components/autumn/pricing-table-preview.tsx @@ -77,7 +77,7 @@ export default function PricingTablePreview({ } else if (productCount === 2) { return "flex flex-col gap-6 max-w-2xl mx-auto px-4 sm:grid sm:grid-cols-2 sm:flex-none"; // Vertical on mobile, 2 columns on sm+ } else { - return "flex flex-col gap-6 max-w-7xl mx-auto px-4 sm:grid sm:grid-cols-2 lg:grid-cols-3 sm:flex-none"; // Vertical on mobile, 2 columns on sm+, 3 on lg+ + return "flex flex-col gap-6 max-w-7xl mx-auto px-4 sm:grid md:grid-cols-2 2xl:grid-cols-3 sm:flex-none"; // Vertical on mobile, 2 columns on sm+, 3 on lg+ } }; diff --git a/vite/src/views/onboarding3/OnboardingPreview.tsx b/vite/src/views/onboarding3/OnboardingPreview.tsx index 6d69c5ae7..33f26b631 100644 --- a/vite/src/views/onboarding3/OnboardingPreview.tsx +++ b/vite/src/views/onboarding3/OnboardingPreview.tsx @@ -1,14 +1,14 @@ import { productV2ToBasePrice } from "@autumn/shared"; import { CrosshairSimpleIcon } from "@phosphor-icons/react"; import { PricingTableContainer } from "@/components/autumn/PricingTableContainer"; -import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { PlanTypeBadges } from "@/components/v2/badges/PlanTypeBadges"; import { IconButton } from "@/components/v2/buttons/IconButton"; +import { Card, CardContent, CardHeader } from "@/components/v2/cards/Card"; import { Separator } from "@/components/v2/separator"; import { useProductsQuery } from "@/hooks/queries/useProductsQuery"; import { useFeatureStore } from "@/hooks/stores/useFeatureStore"; import { useProductStore } from "@/hooks/stores/useProductStore"; -import { useSheetStore } from "@/hooks/stores/useSheetStore"; +import { useIsEditingPlan, useSheetStore } from "@/hooks/stores/useSheetStore"; import { keyToTitle } from "@/utils/formatUtils/formatTextUtils"; import { PlanCardToolbar } from "../products/plan/components/plan-card/PlanCardToolbar"; import { PlanFeatureList } from "../products/plan/components/plan-card/PlanFeatureList"; @@ -32,6 +32,7 @@ export const OnboardingPreview = ({ const playgroundMode = useOnboardingStore((state) => state.playgroundMode); const feature = useFeatureStore((state) => state.feature); const setSheet = useSheetStore((state) => state.setSheet); + const isPlanBeingEdited = useIsEditingPlan(); const handleDeletePlanSuccess = useOnboardingStore( (s) => s.handleDeletePlanSuccess, ); @@ -78,7 +79,7 @@ export const OnboardingPreview = ({ } return ( - +
@@ -110,24 +111,13 @@ export const OnboardingPreview = ({
- {/* {showBasicInfo && product?.description && ( - - {product.description} - - )} */} - - {/* {showBasicInfo && - !(product?.description || product?.name || basePrice?.amount) && ( - - Enter data on the right to see the preview - - )} */} - {showPricing && ( } - className="mt-2 pointer-events-none" + className="mt-2 !opacity-100" + onClick={handleEdit} + disabled={isPlanBeingEdited} > {basePrice?.amount ? ( @@ -141,20 +131,15 @@ export const OnboardingPreview = ({ )} )} - - {showDummyFeature && feature && ( - <> - - - - )} - {/* {!showFeatures && !showDummyFeature && ( - - Create a feature on the right - - )} */} - {showFeatures && }
+ {showDummyFeature && feature && ( + <> + + + + )} + + {showFeatures && } {showFeatures && (
diff --git a/vite/src/views/products/products/product-row-toolbar/ProductRowToolbar.tsx b/vite/src/views/products/products/product-row-toolbar/ProductRowToolbar.tsx index 8e75d3dfb..a4c751abb 100644 --- a/vite/src/views/products/products/product-row-toolbar/ProductRowToolbar.tsx +++ b/vite/src/views/products/products/product-row-toolbar/ProductRowToolbar.tsx @@ -1,6 +1,7 @@ -import type { ProductV2 } from "@autumn/shared"; -import { Archive, ArchiveRestore, Copy, Pen } from "lucide-react"; +import type { ProductCounts, ProductV2 } from "@autumn/shared"; +import { Archive, ArchiveRestore, Copy, Delete, Pen } from "lucide-react"; import { useState } from "react"; +import { useNavigate } from "react-router"; import { ToolbarButton } from "@/components/general/table-components/ToolbarButton"; import { DropdownMenu, @@ -8,26 +9,30 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { DeletePlanDialog } from "../../plan/components/DeletePlanDialog"; import { CopyProductDialog } from "../components/CopyProductDialog"; -import { DeleteProductDialog } from "../components/DeleteProductDialog"; import { UpdateProductDialog } from "../components/UpdateProductDialog"; export const ProductRowToolbar = ({ className, isOnboarding = false, product, + productCounts, }: { isOnboarding?: boolean; className?: string; product: ProductV2; + productCounts: ProductCounts | undefined; }) => { const [dropdownOpen, setDropdownOpen] = useState(false); const [updateOpen, setUpdateOpen] = useState(false); const [copyOpen, setCopyOpen] = useState(false); const [deleteOpen, setDeleteOpen] = useState(false); + const navigate = useNavigate(); - let deleteText = "Archive"; - let DeleteIcon = Archive; + const allCount = productCounts?.all || 0; + let deleteText = allCount > 0 ? "Archive" : "Delete"; + let DeleteIcon = allCount > 0 ? Archive : Delete; if (product.archived) { deleteText = "Unarchive"; @@ -46,11 +51,29 @@ export const ProductRowToolbar = ({ setOpen={setCopyOpen} product={product} /> - { + // pushPage({ + // navigate, + // path: "/products", + // queryParams: { + // tab: "products", + // }, + // preserveParams: true, + // }); + // }} + /> + {/* + + // productCounts={productCounts} + // dropdownOpen={dropdownOpen} + /> */} From 989700205a106bbbf78d006f1049f7b4a332cf60 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Thu, 16 Oct 2025 10:45:41 +0100 Subject: [PATCH 2/2] chore: misc UI improvements and cleanups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Increase toast duration to 6s for better readability - Adjust card gap spacing for better visual consistency - Add separator to plan card header - Update breadcrumb text from "Plans" to "Products" - Improve delete dialog callbacks and navigation - Clean up unused code and console logs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- vite/src/components/general/CustomToaster.tsx | 1 + vite/src/components/v2/cards/Card.tsx | 2 +- vite/src/index.css | 6 ++++- .../plan/components/DeletePlanDialog.tsx | 26 ++++++++++++++++--- .../plan/components/EditPlanHeader.tsx | 2 +- .../plan/components/plan-card/PlanCard.tsx | 7 ++++- .../components/plan-card/PlanCardHeader.tsx | 2 ++ .../components/plan-card/PlanCardToolbar.tsx | 19 ++++++++++++-- .../DeleteProductDialog2.tsx} | 13 ++++++++++ 9 files changed, 69 insertions(+), 9 deletions(-) rename vite/src/views/products/products/components/{DeleteProductDialog.tsx => archives/DeleteProductDialog2.tsx} (96%) diff --git a/vite/src/components/general/CustomToaster.tsx b/vite/src/components/general/CustomToaster.tsx index 656318f49..bb81f0f14 100644 --- a/vite/src/components/general/CustomToaster.tsx +++ b/vite/src/components/general/CustomToaster.tsx @@ -16,6 +16,7 @@ export const CustomToaster = () => { className={isOnboarding ? "" : "flex justify-center"} duration={6000} toastOptions={{ + duration: 6000, className: "w-96 px-3 pt-3 pb-2.5 bg-white rounded-xl shadow-[0_4px_4px_0_rgba(0,0,0,0.02),inset_0_-4px_6px_0_rgba(0,0,0,0.04),0_4px_24px_0_rgba(0,0,0,0.03)] outline outline-16 inline-flex flex-row justify-start items-start gap-3 overflow-hidden", style: { diff --git a/vite/src/components/v2/cards/Card.tsx b/vite/src/components/v2/cards/Card.tsx index 52b6ac875..3e7de3070 100644 --- a/vite/src/components/v2/cards/Card.tsx +++ b/vite/src/components/v2/cards/Card.tsx @@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
void; onDeleteSuccess?: () => Promise; }) => { const axiosInstance = useAxiosInstance(); - const product = useProductStore((s) => s.product); + const storeProduct = useProductStore((s) => s.product); + + let product: ProductV2; + if (propProduct) { + product = propProduct; + } else { + product = storeProduct; + } + const [loading, setLoading] = useState(false); const [deleteAllVersions, setDeleteAllVersions] = useState(false); const { invalidate: invalidateProducts } = useProductsQuery(); @@ -58,13 +69,14 @@ export const DeletePlanDialog = ({ ); await Promise.all([invalidateProducts(), invalidateProduct()]); - setOpen(false); - toast.success("Product deleted successfully"); // Call onDeleteSuccess callback if provided (for onboarding) if (onDeleteSuccess) { await onDeleteSuccess(); } + + setOpen(false); + toast.success("Product deleted successfully"); } catch (error: unknown) { toast.error(getBackendErr(error as AxiosError, "Error deleting product")); } finally { @@ -78,6 +90,10 @@ export const DeletePlanDialog = ({ await ProductService.updateProduct(axiosInstance, product.id, { archived: true, }); + + if (onDeleteSuccess) { + await onDeleteSuccess(); + } toast.success(`${product.name} archived successfully`); setOpen(false); await Promise.all([invalidateProducts(), invalidateProduct()]); @@ -94,6 +110,10 @@ export const DeletePlanDialog = ({ await ProductService.updateProduct(axiosInstance, product.id, { archived: false, }); + + if (onDeleteSuccess) { + await onDeleteSuccess(); + } await refetchProduct(); toast.success(`${product.name} unarchived successfully`); setOpen(false); diff --git a/vite/src/views/products/plan/components/EditPlanHeader.tsx b/vite/src/views/products/plan/components/EditPlanHeader.tsx index 7743237bc..cb28a0d51 100644 --- a/vite/src/views/products/plan/components/EditPlanHeader.tsx +++ b/vite/src/views/products/plan/components/EditPlanHeader.tsx @@ -110,7 +110,7 @@ export const EditPlanHeader = () => { className="p-0" items={[ { - name: "Plans", + name: "Products", href: "/products?tab=products", }, { 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 125c09af7..243476b55 100644 --- a/vite/src/views/products/plan/components/plan-card/PlanCard.tsx +++ b/vite/src/views/products/plan/components/plan-card/PlanCard.tsx @@ -1,6 +1,6 @@ import { useHotkeys } from "react-hotkeys-hook"; import { Card, CardContent } from "@/components/v2/cards/Card"; - +import { Separator } from "@/components/v2/separator"; import { useFeatureNavigation } from "../../hooks/useFeatureNavigation"; import { PlanCardHeader } from "./PlanCardHeader"; import { PlanFeatureList } from "./PlanFeatureList"; @@ -16,6 +16,11 @@ export default function PlanCard() { return ( + +
+ +
+ diff --git a/vite/src/views/products/plan/components/plan-card/PlanCardHeader.tsx b/vite/src/views/products/plan/components/plan-card/PlanCardHeader.tsx index c5a2975d4..9c51e3056 100644 --- a/vite/src/views/products/plan/components/plan-card/PlanCardHeader.tsx +++ b/vite/src/views/products/plan/components/plan-card/PlanCardHeader.tsx @@ -1,5 +1,6 @@ import { mapToProductV3 } from "@autumn/shared"; import { CrosshairSimpleIcon } from "@phosphor-icons/react"; +import { useNavigate } from "react-router"; import { PlanTypeBadges } from "@/components/v2/badges/PlanTypeBadges"; import { IconButton } from "@/components/v2/buttons/IconButton"; import { CardHeader } from "@/components/v2/cards/Card"; @@ -9,6 +10,7 @@ import { keyToTitle } from "@/utils/formatUtils/formatTextUtils"; import { PlanCardToolbar } from "./PlanCardToolbar"; export const PlanCardHeader = () => { + const navigate = useNavigate(); const product = useProductStore((s) => s.product); const setSheet = useSheetStore((s) => s.setSheet); const isPlanBeingEdited = useIsEditingPlan(); diff --git a/vite/src/views/products/plan/components/plan-card/PlanCardToolbar.tsx b/vite/src/views/products/plan/components/plan-card/PlanCardToolbar.tsx index 9b891076e..88b984c7d 100644 --- a/vite/src/views/products/plan/components/plan-card/PlanCardToolbar.tsx +++ b/vite/src/views/products/plan/components/plan-card/PlanCardToolbar.tsx @@ -1,11 +1,13 @@ import { PencilSimpleIcon, TrashIcon } from "@phosphor-icons/react"; import { useState } from "react"; +import { useNavigate } from "react-router"; import { Button } from "@/components/v2/buttons/Button"; import { CopyButton } from "@/components/v2/buttons/CopyButton"; import { IconButton } from "@/components/v2/buttons/IconButton"; import { useProductStore } from "@/hooks/stores/useProductStore"; import { useIsEditingPlan } from "@/hooks/stores/useSheetStore"; import { cn } from "@/lib/utils"; +import { pushPage } from "@/utils/genUtils"; import { DeletePlanDialog } from "../DeletePlanDialog"; interface PlanCardToolbarProps { @@ -18,7 +20,7 @@ interface PlanCardToolbarProps { export const PlanCardToolbar = ({ onEdit, - onDeleteSuccess, + // onDeleteSuccess, editDisabled, deleteDisabled, deleteTooltip, @@ -26,13 +28,26 @@ export const PlanCardToolbar = ({ const product = useProductStore((s) => s.product); const [deleteOpen, setDeleteOpen] = useState(false); const isEditingPlan = useIsEditingPlan(); + const navigate = useNavigate(); + + console.log("deleteOpen", deleteOpen); return ( <> { + console.log("onDeleteSuccess"); + pushPage({ + navigate, + path: "/products", + queryParams: { + tab: "products", + }, + preserveParams: true, + }); + }} />
{product?.id && ( diff --git a/vite/src/views/products/products/components/DeleteProductDialog.tsx b/vite/src/views/products/products/components/archives/DeleteProductDialog2.tsx similarity index 96% rename from vite/src/views/products/products/components/DeleteProductDialog.tsx rename to vite/src/views/products/products/components/archives/DeleteProductDialog2.tsx index bf36df19a..96c94efc5 100644 --- a/vite/src/views/products/products/components/DeleteProductDialog.tsx +++ b/vite/src/views/products/products/components/archives/DeleteProductDialog2.tsx @@ -28,10 +28,12 @@ export const DeleteProductDialog = ({ product, open, setOpen, + onDeleteSuccess, }: { product: ProductV2; open: boolean; setOpen: (open: boolean) => void; + onDeleteSuccess?: () => Promise; }) => { const axiosInstance = useAxiosInstance(); const [loading, setLoading] = useState(false); @@ -55,6 +57,11 @@ export const DeleteProductDialog = ({ await refetchProducts(); setOpen(false); + + if (onDeleteSuccess) { + await onDeleteSuccess(); + } + toast.success("Product deleted successfully"); } catch (error: unknown) { toast.error(getBackendErr(error as AxiosError, "Error deleting product")); @@ -71,6 +78,9 @@ export const DeleteProductDialog = ({ }); toast.success(`${product.name} archived successfully`); setOpen(false); + if (onDeleteSuccess) { + await onDeleteSuccess(); + } await refetchProducts(); } catch (error: unknown) { toast.error( @@ -88,6 +98,9 @@ export const DeleteProductDialog = ({ archived: false, }); await refetchProducts(); + if (onDeleteSuccess) { + await onDeleteSuccess(); + } toast.success(`${product.name} unarchived successfully`); setOpen(false); } catch (error: unknown) {