@@ -6,6 +6,7 @@ import { Link, StyledLink } from "@/components/link";
66import { useRouter } from "@/components/router" ;
77import {
88 ActionCell ,
9+ Alert ,
910 AvatarCell ,
1011 Badge ,
1112 Button ,
@@ -277,6 +278,10 @@ function ProductDetailsSection({ productId, product, config }: ProductDetailsSec
277278 // ===== LOCAL STATE FOR DEFERRED SAVE =====
278279 // Track all pending changes. undefined means "use original value"
279280 const [ pendingChanges , setPendingChanges ] = useState < PendingProductChanges > ( { } ) ;
281+ // Inline validation error shown above the editable grid. We avoid `window.alert()`
282+ // (jarring/blocking) and `toast()` (per AGENTS.md, blocking errors are easily
283+ // missed as toasts) in favor of a destructive Alert in the design system.
284+ const [ saveValidationError , setSaveValidationError ] = useState < string | null > ( null ) ;
280285
281286 // Computed local values (pending change or original)
282287 const localDisplayName = pendingChanges . displayName !== undefined ? pendingChanges . displayName : ( product . displayName || '' ) ;
@@ -308,6 +313,7 @@ function ProductDetailsSection({ productId, product, config }: ProductDetailsSec
308313 // Discard all pending changes
309314 const handleDiscard = ( ) => {
310315 setPendingChanges ( { } ) ;
316+ setSaveValidationError ( null ) ;
311317 // Reset add-on dialog state
312318 setIsAddOn ( product . isAddOnTo !== false && typeof product . isAddOnTo === 'object' ) ;
313319 setSelectedAddOnProducts (
@@ -321,9 +327,10 @@ function ProductDetailsSection({ productId, product, config }: ProductDetailsSec
321327 const handleSave = async ( ) => {
322328 const effectivePrices = pendingChanges . prices ?? product . prices ;
323329 if ( Object . keys ( effectivePrices ) . length === 0 ) {
324- alert ( "A product must have at least one price. Add a price option or make the product free before saving." ) ;
330+ setSaveValidationError ( "A product must have at least one price. Add a price option or make the product free before saving." ) ;
325331 return ;
326332 }
333+ setSaveValidationError ( null ) ;
327334
328335 const configUpdate : Record < string , any > = { } ;
329336
@@ -538,6 +545,10 @@ function ProductDetailsSection({ productId, product, config }: ProductDetailsSec
538545
539546 // ===== PRICES HANDLERS (for deferred save) =====
540547 const handlePricesChange = ( newPrices : Product [ 'prices' ] ) => {
548+ // Clear the "needs at least one price" error as soon as the user adds one back.
549+ if ( Object . keys ( newPrices ) . length > 0 ) {
550+ setSaveValidationError ( null ) ;
551+ }
541552 // Deep compare to see if we're back to original
542553 const originalPrices = product . prices ;
543554 if ( JSON . stringify ( newPrices ) === JSON . stringify ( originalPrices ) ) {
@@ -731,6 +742,11 @@ function ProductDetailsSection({ productId, product, config }: ProductDetailsSec
731742
732743 return (
733744 < >
745+ { saveValidationError && (
746+ < Alert variant = "destructive" className = "mb-4" >
747+ { saveValidationError }
748+ </ Alert >
749+ ) }
734750 < DesignEditableGrid
735751 items = { gridItems }
736752 columns = { 2 }
0 commit comments