Newsletter recommendations tweaks, add enter to blur on validation table

This commit is contained in:
2026-02-03 14:45:13 -05:00
parent 2744e82264
commit a703019b0b
9 changed files with 43 additions and 21 deletions

View File

@@ -31,13 +31,14 @@ const CATEGORY_FILTERS = {
back_in_stock: "AND is_back_in_stock = true",
bestsellers: "AND shop_score > 20 AND COALESCE(current_stock, 0) > 0 AND COALESCE(sales_30d, 0) > 0",
never_featured: "AND times_featured IS NULL AND line_last_featured_at IS NULL",
no_interest: "AND COALESCE(total_sold, 0) = 0 AND COALESCE(current_stock, 0) > 0 AND COALESCE(date_online, product_created_at) <= CURRENT_DATE - INTERVAL '30 days'",
};
function buildScoredCTE({ forCount = false } = {}) {
// forCount=true returns minimal columns for COUNT(*)
const selectColumns = forCount ? `
p.pid,
p.created_at,
p.created_at as product_created_at,
p.date_online,
p.shop_score,
p.preorder_count,

View File

@@ -4313,9 +4313,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001739",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz",
"integrity": "sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==",
"version": "1.0.30001766",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz",
"integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==",
"dev": true,
"funding": [
{

View File

@@ -382,10 +382,12 @@ export function ProductEditForm({
originalImagesRef.current = [...productImages];
reset(data);
} else {
toast.error(result.message ?? "Failed to update product");
if (result.error) {
console.error("Edit error details:", result.error);
}
const errorDetail = Array.isArray(result.error)
? result.error.filter((e) => e !== "Errors").join("; ")
: typeof result.error === "string"
? result.error
: null;
toast.error(errorDetail || result.message || "Failed to update product");
}
} catch (err) {
toast.error(

View File

@@ -45,7 +45,7 @@ interface Props {
data: Product[];
file: File;
onBack?: () => void;
onSubmit: (data: Product[], file: File, options: SubmitOptions) => void | Promise<any>;
onSubmit: (data: Product[], file: File, options: SubmitOptions) => void | Promise<boolean | void>;
}
export const ImageUploadStep = ({
@@ -228,14 +228,16 @@ export const ImageUploadStep = ({
showNewProduct,
};
await onSubmit(updatedData, file, submitOptions);
const success = await onSubmit(updatedData, file, submitOptions);
// Delete the import session on successful submit
try {
await deleteImportSession();
} catch (err) {
// Non-critical - log but don't fail the submission
console.warn('Failed to delete import session:', err);
// Only delete the import session after a successful submit response
if (success) {
try {
await deleteImportSession();
} catch (err) {
// Non-critical - log but don't fail the submission
console.warn('Failed to delete import session:', err);
}
}
} catch (error) {
console.error('Submit error:', error);

View File

@@ -337,7 +337,7 @@ export const UploadFlow = ({ state, onNext, onBack }: Props) => {
invalidData: [] as Data<string>[],
all: data as Data<string>[]
};
onSubmit(result, file, options);
return onSubmit(result, file, options);
}}
/>
)

View File

@@ -113,6 +113,14 @@ const InputCellComponent = ({
setIsFocused(true);
}, [cellPopoverClosedAt]);
// Handle Enter key to blur the field (useful for barcode scanners that send Enter after scan)
const handleKeyDown = useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
e.preventDefault();
inputRef.current?.blur();
}
}, []);
// Update store only on blur - this is when validation runs too
// IMPORTANT: We store FULL precision for price fields to allow accurate calculations
// The display formatting happens separately via displayValue
@@ -151,6 +159,7 @@ const InputCellComponent = ({
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
disabled={isValidating}
className={cn(
'h-8 text-sm',

View File

@@ -331,12 +331,16 @@ const MultilineInputComponent = ({
onBlur(editedSuggestion);
onDismissAiSuggestion?.(); // Clear the suggestion after accepting
setAiSuggestionExpanded(false);
intentionalCloseRef.current = true;
setPopoverOpen(false);
}, [editedSuggestion, onBlur, onDismissAiSuggestion]);
// Handle dismissing the AI suggestion
const handleDismissSuggestion = useCallback(() => {
onDismissAiSuggestion?.();
setAiSuggestionExpanded(false);
intentionalCloseRef.current = true;
setPopoverOpen(false);
}, [onDismissAiSuggestion]);
// Calculate display value
@@ -446,7 +450,7 @@ const MultilineInputComponent = ({
</Button>
{/* Main textarea */}
<div data-col="left" className="flex flex-col min-h-0 w-full lg:w-1/2">
<div data-col="left" className={cn("flex flex-col min-h-0 w-full", hasAiSuggestion && "lg:w-1/2")}>
<div className={cn(hasAiSuggestion ? 'px-3 py-2 bg-accent' : '', 'flex flex-col flex-1 min-h-0')}>
{/* Product name - shown inline on mobile, in measured spacer on desktop */}
{hasAiSuggestion && productName && (

View File

@@ -520,7 +520,7 @@ export function Import() {
return stringValue;
};
const handleData = async (data: ImportResult, _file: File, submitOptions: SubmitOptions) => {
const handleData = async (data: ImportResult, _file: File, submitOptions: SubmitOptions): Promise<boolean> => {
try {
const rows = ((data.all?.length ? data.all : data.validData) ?? []) as Data<string>[];
const formattedRows: NormalizedProduct[] = rows.map((row) => {
@@ -579,7 +579,7 @@ export function Import() {
setStartFromScratch(false);
toast.success(`[DEBUG] Skipped API submission for ${formattedRows.length} product(s)`);
return;
return true;
}
const response = await submitNewProducts({
@@ -638,11 +638,14 @@ export function Import() {
} else {
toast.error(resolvedFailureMessage ?? defaultFailureMessage);
}
return isSuccess;
} catch (error) {
console.error("Import error:", error);
const errorMessage =
error instanceof Error ? error.message : "Failed to import data. Please try again.";
toast.error(errorMessage);
return false;
}
};
@@ -924,7 +927,7 @@ export function Import() {
<Alert className="border-success bg-success/10">
<CheckCircle className="h-4 w-4" style={{ color: 'hsl(var(--success))' }} />
<AlertTitle className="text-success-foreground">Success</AlertTitle>
<AlertDescription className="text-success-foreground">All products created successfully.</AlertDescription>
<AlertDescription className="text-success-foreground">All products created successfully. Please note that images may take a few minutes to finish uploading before they'll be visible in backend.</AlertDescription>
</Alert>
) : createdProducts.length > 0 && erroredProducts.length > 0 ? (
<Alert className="border-warning bg-warning/10">

View File

@@ -14,6 +14,7 @@ const CATEGORIES = [
{ value: "clearance", label: "Clearance" },
{ value: "daily_deals", label: "Daily Deals" },
{ value: "never_featured", label: "Never Featured" },
{ value: "no_interest", label: "No Interest" },
]
export function Newsletter() {