Skip to content

fix(platform): refine toast icons, CSV header detection, and vendor bulk delete#1321

Merged
Israeltheminer merged 6 commits into
mainfrom
fix/import-ux-and-toast-refinements
Apr 10, 2026
Merged

fix(platform): refine toast icons, CSV header detection, and vendor bulk delete#1321
Israeltheminer merged 6 commits into
mainfrom
fix/import-ux-and-toast-refinements

Conversation

@Israeltheminer
Copy link
Copy Markdown
Collaborator

@Israeltheminer Israeltheminer commented Apr 10, 2026

Summary

  • Simplify toaster component to always render variant icons (info icon as default) instead of conditional icon/no-icon layout branches
  • Remove explicit success variant from delete/revoke toasts — the default toast style now handles all cases uniformly
  • Replace brittle heuristic-based CSV header detection (COMMON_HEADER_NAMES dictionary) with a simpler first-row-is-header approach
  • Unify product import mappers: rename excelrecord, make CSV fallback return null so headerless files fail clearly instead of silently misaligning columns
  • Add expectedColumns guidance hints to customer and vendor import forms
  • Add bulk delete support to vendors table with row selection

Test plan

  • Verify toast notifications display correctly with icons for all variants (default, success, destructive)
  • Import a CSV file with headers for customers, vendors, and products — confirm parsing works
  • Import a headerless CSV — confirm it fails with a clear error instead of silently misaligning
  • Import an Excel file for products — confirm the record mapper works
  • Verify vendor bulk delete: select rows, delete, confirm removal
  • Check import forms show "Expected columns" hint text

Summary by CodeRabbit

Release Notes

  • New Features

    • Added bulk deletion functionality for vendors.
    • Introduced expected column hints in customer and vendor import forms.
    • Enhanced toast notifications with default info icon display.
  • Style

    • Refined toast notification styling for consistency across success and error scenarios.

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

📝 Walkthrough

Walkthrough

This PR coordinates changes across multiple components to refactor the toast notification system by removing explicit success variants, enhance the vendor management table with row selection and bulk deletion, streamline product import parsing from row-based CSV mapping to record-based mapping, simplify CSV header detection in the file parsing utility, update import form UI hints, and add corresponding internationalization strings. The changes span toast behavior, file import workflows, data table enhancements, and localization files.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the three main changes: refining toast icons, improving CSV header detection, and adding vendor bulk delete functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/import-ux-and-toast-refinements

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
services/platform/app/components/ui/feedback/toaster.tsx (1)

74-77: ⚠️ Potential issue | 🟡 Minor

Localize the close button aria-label instead of hardcoding English.

Line 76 uses a hardcoded aria-label="Close", which breaks the repo’s i18n/accessibility rule for ARIA attributes.

💡 Suggested fix
+import { useT } from '@/lib/i18n/client';
+
 export function Toaster() {
+  const { t: tCommon } = useT('common');
   const { toasts } = useToast();
@@
               <ToastPrimitives.Close
                 className="text-foreground/50 hover:text-foreground absolute top-2.5 right-2.5 rounded-md p-1 opacity-0 transition-opacity group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 focus:opacity-100 focus:ring-2 focus:outline-none group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600"
-                aria-label="Close"
+                aria-label={tCommon('actions.close')}
               >

As per coding guidelines: “Do NOT hardcode text” and “ALWAYS use translation keys for aria-label values. Never hardcode English in ARIA attributes”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/components/ui/feedback/toaster.tsx` around lines 74 -
77, Replace the hardcoded aria-label on the ToastPrimitives.Close element with a
localized string: import/obtain the repo's translation helper (e.g., t or
useTranslation) in the component and use the appropriate translation key (e.g.,
"close" or "toast.close") for the aria-label on ToastPrimitives.Close so ARIA
text is not hardcoded; update the Toast component to call the translator and
pass the translated value into aria-label on ToastPrimitives.Close.
services/platform/app/hooks/__tests__/use-file-import.test.ts (1)

158-208: ⚠️ Potential issue | 🟡 Minor

Add coverage for the header-driven CSV path, not just record().

These tests only exercise productMappers.record() directly. They would not catch regressions in the new first-row-is-header flow, the headerless failure path, or the current parseCSV() vs parseFile() mismatch for products. As per coding guidelines, "Tests should cover happy paths, edge cases, and error conditions."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/hooks/__tests__/use-file-import.test.ts` around lines
158 - 208, Add unit tests that exercise the header-driven CSV import flow in
addition to productMappers.record(): write tests that call parseCSV (and/or the
product import wrapper that reads first-row-as-header) with a CSV where the
first row contains header names (including mixed case like
ImageUrl/imageUrl/imageurl) to ensure headers map to product fields, a test
where the CSV has no header row to verify the headerless failure path returns
the expected error/behavior, and a test that exercises the parseFile vs parseCSV
code path mismatch for products to assert both functions produce the same
product mapping; reference productMappers.record, parseCSV, and parseFile to
locate and extend existing tests and assert on mapped fields (name/title
fallback, defaults, imageUrl normalization, and error cases).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@services/platform/app/hooks/use-file-import.ts`:
- Around line 246-250: The csv row mapper in useFileImport currently returns
null (csv: (_row...) => { return null; }) which causes parseCSV to drop all rows
because parseCSVWithMapper is invoked without a recordMapper; fix by having the
csv mapper translate a headered CSV row into a record and forward to the
existing record mapper (or simply delegate to record: (r)=>...): replace the csv
mapper implementation so it builds a Record<string, unknown> from the header/row
pair and calls the existing record mapper or returns its result, ensuring
parseCSV and parseCSVWithMapper produce the same parsed output as file-based CSV
imports.

In `@services/platform/lib/utils/file-parsing.ts`:
- Around line 95-96: The CSV parsing currently only treats the first row as
headers when rows.length >= 2, which misses single-row header-only files; change
the header extraction condition in the parsing logic that sets headers (the
block using rows.shift()?.map((h) => h.toLowerCase()) ?? null) to run whenever
any row exists (e.g., rows.length > 0 or rows.length >= 1) so the first row is
always treated as headers when present, preserving the lowercase mapping and
existing fallback behavior.

In `@services/platform/messages/de.json`:
- Line 3158: Update the German localization string for the key "statusNote" so
the status is quoted and reads naturally in UI copy; replace the awkward value
"Importierte Kunden werden als (Aktiv) markiert" with a version using German
typographic quotes, e.g. "Importierte Kunden werden als „Aktiv“ markiert"
(ensure you update the value for the "statusNote" key in
services/platform/messages/de.json).

In `@services/platform/messages/en.json`:
- Around line 3273-3278: The importForm namespace in the translations is missing
the formatHint key referenced by t('importForm.formatHint') in
vendor-import-form.tsx; add an "formatHint" entry under "importForm" in
services/platform/messages/en.json (and add the same key/value in
services/platform/messages/de.json) so the manual vendor import hint renders
correctly, keeping the string similar to the existing localeHint/expectedColumns
entries.

---

Outside diff comments:
In `@services/platform/app/components/ui/feedback/toaster.tsx`:
- Around line 74-77: Replace the hardcoded aria-label on the
ToastPrimitives.Close element with a localized string: import/obtain the repo's
translation helper (e.g., t or useTranslation) in the component and use the
appropriate translation key (e.g., "close" or "toast.close") for the aria-label
on ToastPrimitives.Close so ARIA text is not hardcoded; update the Toast
component to call the translator and pass the translated value into aria-label
on ToastPrimitives.Close.

In `@services/platform/app/hooks/__tests__/use-file-import.test.ts`:
- Around line 158-208: Add unit tests that exercise the header-driven CSV import
flow in addition to productMappers.record(): write tests that call parseCSV
(and/or the product import wrapper that reads first-row-as-header) with a CSV
where the first row contains header names (including mixed case like
ImageUrl/imageUrl/imageurl) to ensure headers map to product fields, a test
where the CSV has no header row to verify the headerless failure path returns
the expected error/behavior, and a test that exercises the parseFile vs parseCSV
code path mismatch for products to assert both functions produce the same
product mapping; reference productMappers.record, parseCSV, and parseFile to
locate and extend existing tests and assert on mapped fields (name/title
fallback, defaults, imageUrl normalization, and error cases).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6b9d26eb-23e6-41a1-8995-a8eec164b895

📥 Commits

Reviewing files that changed from the base of the PR and between 4f46272 and 40d3894.

📒 Files selected for processing (16)
  • services/platform/app/components/ui/entity/entity-delete-dialog.tsx
  • services/platform/app/components/ui/feedback/toaster.tsx
  • services/platform/app/features/agents/components/agent-delete-dialog.tsx
  • services/platform/app/features/customers/components/customer-import-form.tsx
  • services/platform/app/features/products/components/products-import-dialog.tsx
  • services/platform/app/features/settings/api-keys/components/api-key-revoke-dialog.tsx
  • services/platform/app/features/settings/providers/components/providers-table.tsx
  • services/platform/app/features/settings/teams/components/team-delete-dialog.tsx
  • services/platform/app/features/vendors/components/vendor-import-form.tsx
  • services/platform/app/features/vendors/components/vendors-table.tsx
  • services/platform/app/features/vendors/hooks/use-vendors-table-config.tsx
  • services/platform/app/hooks/__tests__/use-file-import.test.ts
  • services/platform/app/hooks/use-file-import.ts
  • services/platform/lib/utils/file-parsing.ts
  • services/platform/messages/de.json
  • services/platform/messages/en.json
💤 Files with no reviewable changes (4)
  • services/platform/app/features/agents/components/agent-delete-dialog.tsx
  • services/platform/app/components/ui/entity/entity-delete-dialog.tsx
  • services/platform/app/features/settings/api-keys/components/api-key-revoke-dialog.tsx
  • services/platform/app/features/settings/teams/components/team-delete-dialog.tsx

Comment on lines +246 to +250
csv: (_row: string[], _index: number) => {
return null;
},
excel: (record: Record<string, unknown>) => {
/** Record-based mapper used by both CSV (with headers) and Excel imports */
record: (record: Record<string, unknown>) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This breaks useFileImport().parseCSV() for valid product CSVs.

Line 247 makes the CSV mapper reject every row, but parseCSV() in this hook still calls parseCSVWithMapper(csvText, csvMapper) without a recordMapper. So a valid headered product CSV parses correctly from a File, but the same CSV text comes back empty through parseCSV().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/hooks/use-file-import.ts` around lines 246 - 250, The
csv row mapper in useFileImport currently returns null (csv: (_row...) => {
return null; }) which causes parseCSV to drop all rows because
parseCSVWithMapper is invoked without a recordMapper; fix by having the csv
mapper translate a headered CSV row into a record and forward to the existing
record mapper (or simply delegate to record: (r)=>...): replace the csv mapper
implementation so it builds a Record<string, unknown> from the header/row pair
and calls the existing record mapper or returns its result, ensuring parseCSV
and parseCSVWithMapper produce the same parsed output as file-based CSV imports.

Comment on lines 95 to 96
if (rows.length >= 2) {
headers = rows.shift()?.map((h) => h.toLowerCase()) ?? null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Treat the first row as headers whenever a row exists.

Lines 95-96 leave single-row CSVs on the positional path. That means a header-only customer/vendor file like email,name,locale is parsed as one real record, and a single-row headerless file still bypasses the new header-required flow. This can create bogus imports and makes the new behavior inconsistent.

Suggested fix
-  if (rows.length >= 2) {
+  if (rows.length > 0) {
     headers = rows.shift()?.map((h) => h.toLowerCase()) ?? null;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (rows.length >= 2) {
headers = rows.shift()?.map((h) => h.toLowerCase()) ?? null;
if (rows.length > 0) {
headers = rows.shift()?.map((h) => h.toLowerCase()) ?? null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/lib/utils/file-parsing.ts` around lines 95 - 96, The CSV
parsing currently only treats the first row as headers when rows.length >= 2,
which misses single-row header-only files; change the header extraction
condition in the parsing logic that sets headers (the block using
rows.shift()?.map((h) => h.toLowerCase()) ?? null) to run whenever any row
exists (e.g., rows.length > 0 or rows.length >= 1) so the first row is always
treated as headers when present, preserving the lowercase mapping and existing
fallback behavior.

"formatHint": "Format: E-Mail, Name (optional), Sprache (optional). Ein Kunde pro Zeile.",
"localeHint": "Unterstützte Sprachen: en, fr, de, zh, sowie erweiterte Formate wie en-US, de-DE und de-CH.",
"statusNote": "Importierte Kunden werden als (Aktiv) markiert"
"statusNote": "Importierte Kunden werden als (Aktiv) markiert",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Polish the German status note wording.

Line 3158 reads awkwardly: als (Aktiv) markiert. Prefer quoted status text for natural UI copy.

Suggested copy fix
-      "statusNote": "Importierte Kunden werden als (Aktiv) markiert",
+      "statusNote": "Importierte Kunden werden als „Aktiv“ markiert",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"statusNote": "Importierte Kunden werden als (Aktiv) markiert",
"statusNote": "Importierte Kunden werden als Aktiv" markiert",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/messages/de.json` at line 3158, Update the German
localization string for the key "statusNote" so the status is quoted and reads
naturally in UI copy; replace the awkward value "Importierte Kunden werden als
(Aktiv) markiert" with a version using German typographic quotes, e.g.
"Importierte Kunden werden als „Aktiv“ markiert" (ensure you update the value
for the "statusNote" key in services/platform/messages/de.json).

Comment on lines 3273 to 3278
"importForm": {
"manualEntry": "Manual entry",
"upload": "Upload",
"formatHint": "Format: email, name (optional), locale (optional). One vendor per line.",
"localeHint": "You can use the following supported locales: en, fr, de, zh, as well as extended formats like en-US, de-DE, and de-CH."
"localeHint": "You can use the following supported locales: en, fr, de, zh, as well as extended formats like en-US, de-DE, and de-CH.",
"expectedColumns": "Expected columns: email, name, locale"
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing vendors.importForm.formatHint translation key breaks manual vendor import hint.

services/platform/app/features/vendors/components/vendor-import-form.tsx (Line 96) still calls t('importForm.formatHint'), but this namespace does not define formatHint. Add it here (and mirror in services/platform/messages/de.json) to avoid missing-key rendering.

💡 Suggested patch (en.json)
   "importForm": {
     "manualEntry": "Manual entry",
     "upload": "Upload",
+    "formatHint": "Format: email, name (optional), locale (optional). One vendor per line.",
     "localeHint": "You can use the following supported locales: en, fr, de, zh, as well as extended formats like en-US, de-DE and de-CH.",
     "expectedColumns": "Expected columns: email, name, locale"
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"importForm": {
"manualEntry": "Manual entry",
"upload": "Upload",
"formatHint": "Format: email, name (optional), locale (optional). One vendor per line.",
"localeHint": "You can use the following supported locales: en, fr, de, zh, as well as extended formats like en-US, de-DE, and de-CH."
"localeHint": "You can use the following supported locales: en, fr, de, zh, as well as extended formats like en-US, de-DE, and de-CH.",
"expectedColumns": "Expected columns: email, name, locale"
},
"importForm": {
"manualEntry": "Manual entry",
"upload": "Upload",
"formatHint": "Format: email, name (optional), locale (optional). One vendor per line.",
"localeHint": "You can use the following supported locales: en, fr, de, zh, as well as extended formats like en-US, de-DE, and de-CH.",
"expectedColumns": "Expected columns: email, name, locale"
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/messages/en.json` around lines 3273 - 3278, The importForm
namespace in the translations is missing the formatHint key referenced by
t('importForm.formatHint') in vendor-import-form.tsx; add an "formatHint" entry
under "importForm" in services/platform/messages/en.json (and add the same
key/value in services/platform/messages/de.json) so the manual vendor import
hint renders correctly, keeping the string similar to the existing
localeHint/expectedColumns entries.

Comment thread services/platform/messages/de.json Outdated
"upload": "Upload",
"formatHint": "Format: E-Mail, Name (optional), Sprache (optional). Ein Lieferant pro Zeile.",
"localeHint": "Du kannst die folgenden unterstützten Sprachen verwenden: en, fr, de, zh, sowie erweiterte Formate wie en-US, de-DE und de-CH."
"localeHint": "Du kannst die folgenden unterstützten Sprachen verwenden: en, fr, de, zh, sowie erweiterte Formate wie en-US, de-DE und de-CH.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Locale is not restricted anylonger

Comment thread services/platform/messages/en.json Outdated
"upload": "Upload",
"formatHint": "Format: email, name (optional), locale (optional). One vendor per line.",
"localeHint": "You can use the following supported locales: en, fr, de, zh, as well as extended formats like en-US, de-DE, and de-CH."
"localeHint": "You can use the following supported locales: en, fr, de, zh, as well as extended formats like en-US, de-DE, and de-CH.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Locale is not restricted anylonger

providerName: deleteProvider.name,
});
toast({ title: t('providers.deleted'), variant: 'success' });
toast({ title: t('providers.deleted') });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I merged some code adding success variant, you may also wanna remove it for those toast messages.

…ulk delete

- Simplify toaster to always show variant icons (info default) instead of
  conditional icon/no-icon layout
- Remove explicit 'success' variant from delete/revoke toasts (default handles it)
- Replace brittle heuristic-based CSV header detection with first-row-is-header
  approach and drop the COMMON_HEADER_NAMES dictionary
- Unify product import mappers: rename excel→record, make CSV fallback return null
  so headerless files fail clearly instead of silently misaligning columns
- Add expectedColumns hints to customer and vendor import forms
- Add bulk delete support to vendors table with row selection
… hint text

- Change CSV header detection from rows.length >= 2 to rows.length > 0
  so a header-only CSV is correctly treated as headers with zero data rows
- Update localeHint text to reflect that locale is no longer restricted
  to a fixed set (now accepts any valid BCP 47 tag)
@Israeltheminer Israeltheminer force-pushed the fix/import-ux-and-toast-refinements branch from 3d72877 to 922b1b8 Compare April 10, 2026 12:47
Israeltheminer and others added 2 commits April 10, 2026 14:03
…ions

Delete and revoke confirmations are neutral acknowledgements, not
celebratory successes — show the info icon instead of the green check.
@Israeltheminer Israeltheminer merged commit 0988ca5 into main Apr 10, 2026
24 checks passed
@Israeltheminer Israeltheminer deleted the fix/import-ux-and-toast-refinements branch April 10, 2026 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants