Skip to content

Comments

feat: add worker to upload CSV file#3117

Open
icaroov wants to merge 36 commits intovtex:feat/quick-order-by-file-feature-branchfrom
cubos-vtex:feat/BE-71
Open

feat: add worker to upload CSV file#3117
icaroov wants to merge 36 commits intovtex:feat/quick-order-by-file-feature-branchfrom
cubos-vtex:feat/BE-71

Conversation

@icaroov
Copy link
Collaborator

@icaroov icaroov commented Nov 12, 2025

What's the purpose of this pull request?

This pull request introduces a new file upload and CSV parsing feature to the components package, including reusable hooks and UI components. The main changes are the addition of a FileUploadCard component for uploading files, a useCSVParser hook for parsing CSV files in a web worker, and a useFileUpload hook for managing file upload errors and validation. These new utilities are exported for use in other parts of the application.

This PR it was created from #3109

How it works?

New hooks and utilities:

  • Added useCSVParser hook for parsing CSV files in a web worker, with error handling and CSV template generation.
  • Added useFileUpload hook to manage file upload errors, validation, and rejection reasons.
  • Exported new hooks from the hooks index for easier consumption.
  • Added type exports for CSV parsing to the main index file.

New UI components:

  • Added FileUploadCard component for file selection, drag-and-drop, and template download, with error state and file validation.
  • Added exports for FileUploadCard and FileUploadStatus components and their types.

Dependency updates:

  • Added papaparse to dependencies for running CSV parsing in a web worker.

How to test it?

Nov-12-2025 14-49-47

Starters Deploy Preview

References

Checklist

You may erase this after checking them all 😉

PR Title and Commit Messages

  • PR title and commit messages follow the Conventional Commits specification
    • Available prefixes: feat, fix, chore, docs, style, refactor, ci and test

PR Description

  • Added a label according to the PR goal - breaking change, bug, contributing, performance, documentation..

Dependencies

  • Committed the pnpm-lock.yaml file when there were changes to the packages

Documentation

  • PR description
  • For documentation changes, ping @Mariana-Caetano to review and update (Or submit a doc request)

Summary by CodeRabbit

  • New Features

    • CSV parsing with worker-backed parsing, template generation, Dropzone upload UI and an Upload File dropdown; file-based search integration.
  • Enhancements

    • New file upload hooks, FileUploadCard and FileUploadStatus props for richer status, customizable labels, and improved file name/size formatting.
    • Template download and clearer upload error handling.
  • Refactor

    • Tightened public type signatures and expanded component/hook exports.
  • Style

    • Added Dropzone and upload-related styles and Storybook stories.

@icaroov icaroov self-assigned this Nov 12, 2025
@icaroov icaroov requested a review from a team as a code owner November 12, 2025 17:53
@icaroov icaroov requested review from BrunaCubos, emersonlaurentino and hellofanny and removed request for a team November 12, 2025 17:53
@icaroov icaroov changed the base branch from main to feat/quick-order-by-file-feature-branch November 21, 2025 15:01
@giurigaud
Copy link

The changelog needs to be added.

@codesandbox-ci
Copy link

codesandbox-ci bot commented Dec 3, 2025

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

@ArthurTriis1
Copy link
Contributor

Adiciona por favor na descrição do PR o arquivo .csv que estão usando no desenvolvimento

"react-dom": "^18.2.0"
},
"dependencies": {
"papaparse": "^5.5.3",
Copy link
Contributor

Choose a reason for hiding this comment

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

Precisamos checar com o time da faststore a opinião sobre a adição desse pacote, consegue explicar um pouco a necessidade dele?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Foi utilizado visando desempenho e simplicidade para processar arquivos CSV grandes. Foi indicado o uso na RFC. Ele lê o CSV em partes (streaming), não trava a tela, já usa Web Worker, detecta separador, trata aspas, dá progresso, permite cancelar e manipula arquivos grandes sem prejudicar a memória.

Comment on lines 13 to 15
'application/vnd.ms-excel': ['.xls'],
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': [
'.xlsx',
Copy link
Contributor

Choose a reason for hiding this comment

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

Acredito que na RFC foi proposto o upload apenas de formato csv, surgiu a necessidade de xls e xlsx tb?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ajustado, aceitando apenas CSV

Copy link
Contributor

@ArthurTriis1 ArthurTriis1 left a comment

Choose a reason for hiding this comment

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

A funcionalidade parece estar funcionando bem, mas reforço que precisamos da avaliação do time da faststore sobre a adição da lib papaparse e averiguar se devemos de fato permitir o upload de xls e xlsx.

@BrunaCubos
Copy link
Collaborator

A funcionalidade parece estar funcionando bem, mas reforço que precisamos da avaliação do time da faststore sobre a adição da lib papaparse e averiguar se devemos de fato permitir o upload de xls e xlsx.

A adição da lib do papaparse foi indicado para uso na RFC.

@BrunaCubos
Copy link
Collaborator

Para desenvolver utilizamos o arquvio fornecido pela propria RFC
FastStore Starter Template.csv

@lariciamota
Copy link
Contributor

Oie, pessoal!
Notei que nesse PR tem alguns arquivos do #3109, acredito que esteja faltando rebase e mudar a branch de destino para não ficar misturando. Como o PR anterior também está em revisão e esses arquivos têm mudanças pedidas lá, seria bom ficar com as mudanças separadas para facilitar review e não ter comentários repetidos. Obrigada!

giurigaud and others added 2 commits January 29, 2026 14:20
## What's the purpose of this pull request?

This PR refers to the creation of the Quick Order Drawer, a new
component developed for the Quick Order By File functionality.

The main advantage of the Quick Order functionality is the ability to
add products directly to the cart from the search, which significantly
reduces the number of clicks and browsing time.

<!--- Considering the context, what is the problem we'll solve? Where in
VTEX's big picture our issue fits in? Write a tweet about the context
and the problem itself. --->

## How it works?

<!--- Tell us the role of the new feature, or component, in its context.
Provide details about what you have implemented and screenshots if
applicable. --->
Creating the Quick Order Drawer layout according to Figma.

<img width="1912" height="966" alt="image (3)"
src="https://github.com/user-attachments/assets/65a66428-8db2-45f4-86f5-c28f4236a2e4"
/>

## How to test it?

<!--- Describe the steps with bullet points. Is there any external link
that can be used to better test it or an example? --->

	•	The user uploads the file in the Quick Order interface.
	•	After the upload is completed, the Search button becomes enabled.
	•	The user clicks the Search button to start processing.
	•	The system begins processing the file data.
	•	The Dropdown enters a loading state.
• Even while loading, the Dropdown can be closed without interrupting
the processing.
• Once processing is finished, the system automatically opens the Quick
Order Drawer.


### Starters Deploy Preview

<!--- Add a link to a deploy preview from `starter.store` with this
branch being used. --->

<!--- Tip: You can get an installable version of this branch from the
CodeSandbox generated when this PR is created. --->

## References


[Figma](https://www.figma.com/design/0c3DOaXs6rS25LyDWi7uap/Cubos-%C2%B7-FastStore-Features-(H2-2024)?node-id=1-23&node-type=canvas&t=EiFoKx4kFwgKhNEP-0)

<!--- Spread the knowledge: is there any content you used to create this
PR that is worth sharing? --->

<!--- Extra tip: adding references to related issues or mentioning
people important to this PR may be good for the documentation and
reviewing process --->

## Checklist

<em>You may erase this after checking them all 😉</em>

**PR Title and Commit Messages**

- [x] PR title and commit messages follow the [Conventional
Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification
- Available prefixes: `feat`, `fix`, `chore`, `docs`, `style`,
`refactor`, `ci` and `test`

**PR Description**

- [ ] Added a label according to the PR goal - `breaking change`, `bug`,
`contributing`, `performance`, `documentation`..

**Dependencies**

- [ ] Committed the `pnpm-lock.yaml` file when there were changes to the
packages

**Documentation**

- [x] PR description
- [ ] For documentation changes, ping `@Mariana-Caetano` to review and
update (Or submit a doc request)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added Quick Order Drawer component enabling streamlined product
ordering with header, product selection table, quantity adjustments, and
checkout from a side panel.

* **Documentation**
* Added Storybook examples demonstrating Quick Order Drawer with
multiple configuration scenarios.

* **Chores**
* Updated TypeScript React type definitions to latest compatible
versions.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: renato <renato.neto@cubos.io>
Co-authored-by: Bruna Santos <brunassdev@gmail.com>
Co-authored-by: Arthur Andrade <arthurfelandrade@gmail.com>
Tasks
[https://vtex-dev.atlassian.net/jira/software/projects/BE/boards/2868?selectedIssue=BE-69](https://vtex-dev.atlassian.net/jira/software/projects/BE/boards/2868?selectedIssue=BE-69)
[BE-70](https://vtex-dev.atlassian.net/jira/software/projects/BE/boards/2868?selectedIssue=BE-70)

Here's the completed PR description in English:

- Introduced `FileUploadCard` for file selection and upload
functionality, including drag-and-drop support.
- Added `FileUploadStatus` to display upload progress and error
handling.
- Updated index files to export new components and types.
- Enhanced `SearchInputField` to include an attachment button that
triggers the file upload card.
- Added styles for both new components to ensure proper UI integration.

## What's the purpose of this pull request?

This PR introduces a new file upload feature that allows users to
perform bulk searches by uploading CSV files. The feature provides a
modern, user-friendly interface with drag-and-drop support, real-time
upload status tracking, and comprehensive error handling to guide users
through the file upload process.

## How it works?

### FileUploadCard Component
- **File Selection**: Users can either click "Select file" button or
drag-and-drop files into the dropzone
- **Drag & Drop**: Visual feedback with border color and background
changes when dragging files
- **File Input**: Hidden file input that's always present in the DOM to
ensure file picker works in all states
- **Download Template**: Provides a CSV template download option for
users who need guidance on file format
- **Validation**: Only accepts CSV files (`.csv` extension)
- **States**: Manages upload states (uploading, completed, error) with
appropriate UI updates

### FileUploadStatus Component
Displays the current status of the uploaded file with three distinct
states:

1. **Uploading State**
   - Shows spinning CircleNotch icon
   - Displays "Uploading your file..." message
   - Shows filename

2. **Completed State**
   - Shows Table icon in green background
   - Displays filename and file size
   - Shows "Search" button to proceed with the uploaded file

3. **Error State**
   - Shows WarningOctagon icon in red background
- Special styling: light pink background (#FDF6F5) with red border
(#FFDFD9)
   - Displays error title and description in two lines
   - Shows two action buttons: "Download template" and "Select file"

### Error Types
The component handles six different error scenarios:
- **Unsupported**: Wrong file type (non-CSV files)
- **Unreadable**: File can't be parsed or is corrupted
- **Invalid Structure**: Missing headers or incorrect columns
- **Empty**: File has no data
- **Too Large**: File exceeds size limit
- **Unexpected**: Generic error for unknown issues

### Integration
- Added attachment icon button to `SearchInputField` component
- Clicking the attachment button toggles the `FileUploadCard` visibility
- Card appears as a dropdown below the search input
- Smooth animations for show/hide transitions

### Key Features
- **File validation**: Automatically checks file type on selection/drop
- **Error recovery**: Users can easily select a new file or download
template when errors occur
- **Responsive design**: Components adapt to container width
- **Accessibility**: Proper ARIA labels and keyboard navigation support
- **Visual feedback**: Icons, colors, and animations provide clear
status indication

## How to test it?

### Basic File Upload Flow
1. Navigate to a page with the search input
2. Click the attachment icon button (Paperclip icon)
3. The FileUploadCard should appear below the search input
4. Click "Select file" or drag-and-drop a CSV file
5. Observe the uploading state with spinning icon
6. After 2 seconds, see the completed state with "Search" button
7. Click the X button to remove the file and return to initial state

### Drag and Drop
1. Open the FileUploadCard
2. Drag a CSV file over the dropzone
3. Observe the visual feedback (border and background color change)
4. Drop the file to upload

### Error Handling - Unsupported File Type
1. Open the FileUploadCard
2. Select or drop a non-CSV file (e.g., .txt, .pdf, .xlsx)
3. Observe error state with red icon and pink background
4. Read error message: "Unsupported file type." / "Upload a CSV or use
the template provided."
5. Click "Download template" to get a sample CSV
6. Click "Select file" to try uploading again

### File Removal
1. Upload a file successfully
2. Click the X button in the top-right of the file status
3. Component should return to initial dropzone state


### Starters Deploy Preview

<!-- Add your deploy preview link here -->

## References

- [Phosphor Icons](https://phosphoricons.com/) - Icon library used for
UI elements
- [Conventional Commits](https://www.conventionalcommits.org/) - Commit
message format

## Checklist

<em>You may erase this after checking them all 😉</em>

**PR Title and Commit Messages**

- [ ] PR title and commit messages follow the [Conventional
Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification
- Available prefixes: `feat`, `fix`, `chore`, `docs`, `style`,
`refactor`, `ci` and `test`

**PR Description**

- [ ] Added a label according to the PR goal - `breaking change`, `bug`,
`contributing`, `performance`, `documentation`..

**Dependencies**

- [ ] Committed the `pnpm-lock.yaml` file when there were changes to the
packages

**Documentation**

- [ ] PR description


[BE-70]:
https://vtex-dev.atlassian.net/browse/BE-70?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* File upload card component enabling file selection via drag-and-drop
or file picker with status indicators
* File upload status component displaying upload progress, success, and
error states with customizable error handling
  * Attachment button in search input for quick file upload access

* **Chores**
  * Added component styling and Storybook demonstrations

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Bruna Santos <brunassdev@gmail.com>
Co-authored-by: Arthur Andrade <arthurfelandrade@gmail.com>
@icaroov icaroov removed their assignment Feb 6, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds CSV upload and parsing with a Dropzone UI: new hooks (useCSVParser, useFileUpload), Dropzone and Dropzone styles, UploadFileDropdown integrated into SearchInput, FileUploadCard/FileUploadStatus prop changes, utilities for file formatting, dependency additions, and updated exports. (50 words)

Changes

Cohort / File(s) Summary
Dependencies
packages/components/package.json, packages/ui/package.json, packages/storybook/package.json
Added react-dropzone, papaparse, @types/papaparse; switched some internal refs to workspace:*.
Hooks
packages/components/src/hooks/useCSVParser.ts, packages/components/src/hooks/useFileUpload.ts, packages/components/src/hooks/index.ts
Added useCSVParser (worker-based PapaParse, validation, template generation, progress) and useFileUpload (rejection/error mapping, defaults); re-exported them.
Dropzone component
packages/components/src/molecules/Dropzone/Dropzone.tsx, packages/components/src/molecules/Dropzone/index.ts, packages/ui/src/components/molecules/Dropzone/styles.scss
New Dropzone wrapping react-dropzone with accept/reject handlers, drag state, file lists, and SCSS; exports DropzoneProps/DropzoneState.
Upload UI (core)
packages/core/src/components/search/UploadFileDropdown/..., .../section.module.scss, .../index.ts
New UploadFileDropdown using useCSVParser + Dropzone, toasts, template download, parsed-results panel, labels/types and re-exports.
SearchInput integration
packages/core/src/components/search/SearchInput/SearchInput.tsx, packages/core/src/components/search/fileUploadCardDefaults.ts
Integrated CSV upload: onFileSearch prop, explicit fileUploadCardProps shape, uploadFileDropdownLabels, defaults, CSV parsing state and handlers.
File upload components
packages/components/src/molecules/FileUploadCard/FileUploadCard.tsx, packages/components/src/molecules/FileUploadStatus/FileUploadStatus.tsx
Adjusted prop surface: several label/error props made required; added formatterFileSize, formatterFileName, isUploading, hasError, optional fileName; updated internal usage and template content.
Polymorphic typing updates
packages/components/src/atoms/Link/Link.tsx, packages/components/src/atoms/List/List.tsx, packages/components/src/molecules/Accordion/AccordionItem.tsx
Tightened forwardRef typings to concrete elements (HTMLAnchorElement, HTMLUListElement, HTMLDivElement) and used Omit<..., 'ref'>, changing exported type signatures only.
Utilities & UI tweaks
packages/core/src/utils/utilities.ts, packages/core/src/components/sections/Navbar/Navbar.tsx, packages/core/src/components/search/SearchInputField/SearchInputField.tsx, packages/ui/src/components/.../SearchInputField/styles.scss
Added formatFileName and formatFileSize; applied default upload card props in Navbar; added SearchInputFieldProps and input styling adjustments.
Public exports, stories & changelogs
packages/components/src/index.ts, packages/components/CHANGELOG.md, packages/core/CHANGELOG.md, packages/storybook/stories/*, various SCSS files
Exported CSV types and Dropzone; added stories for Dropzone/UploadFileDropdown/FileUpload; updated changelogs and included Dropzone styles.

Sequence Diagram

sequenceDiagram
    autonumber
    actor User as "User"
    participant SI as "SearchInput"
    participant UFD as "UploadFileDropdown"
    participant DZ as "Dropzone"
    participant CSV as "useCSVParser"
    participant WW as "WebWorker (PapaParse)"
    participant FUS as "FileUploadStatus"

    User->>SI: open upload UI
    SI->>UFD: render dropdown
    User->>DZ: drop/select CSV file
    DZ->>UFD: onFilesAccepted(files)
    UFD->>CSV: onParseFile(file)
    CSV->>WW: parse (worker: true, chunked)
    WW-->>CSV: chunk results + progress
    CSV-->>UFD: resolve CSVData (rows, totals)
    UFD->>FUS: display results / status
    User->>FUS: click Search
    FUS->>SI: emit parsed CSVData
    SI->>SI: call onFileSearch(CSVData)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • lemagnetic
  • lariciamota

Poem

📂✨ A file drifts into Dropzone light,
PapaParse hums through worker night,
Rows align and errors fade away,
SearchInput sings the parsed array,
Bulk upload joy — CSV saved for day!

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main feature addition: CSV file upload functionality using a web worker via PapaParse.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into feat/quick-order-by-file-feature-branch

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@ArthurTriis1 ArthurTriis1 force-pushed the feat/quick-order-by-file-feature-branch branch from 6a8e751 to c197935 Compare February 10, 2026 14:05
…resolve conflicts

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link

@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: 20

Caution

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

⚠️ Outside diff range comments (4)
packages/ui/src/components/molecules/SearchInputField/styles.scss (1)

79-142: ⚠️ Potential issue | 🟡 Minor

Duplicate rule blocks — lines 79–105 are repeated verbatim at lines 115–142.

Three selectors are defined twice within [data-fs-search-input-field]:

Selector First occurrence Duplicate
[data-fs-icon-button][data-fs-button-variant] 79–87 115–124
[data-fs-search-input-field-attachment-button] 89–97 126–134
[data-fs-search-input-field-separator] 99–105 136–142

This looks like a rebase/merge artifact. Remove one set to avoid confusion and potential specificity surprises.

Proposed fix — remove duplicate blocks
-  [data-fs-icon-button][data-fs-button-variant] {
-    `@include` media(">=notebook") {
-      min-height: var(--fs-search-input-field-button-min-height);
-      padding-top: var(--fs-search-input-field-button-padding-top-desktop);
-      padding-bottom:
-        var(
-          --fs-search-input-field-button-padding-bottom-desktop
-        );
-    }
-  }
-
-  [data-fs-search-input-field-attachment-button] {
-    [data-fs-icon] {
-      transform: rotate(90deg);
-    }
-
-    &:hover {
-      --fs-button-border-radius: var(--fs-border-radius-circle);
-    }
-  }
-
-  [data-fs-search-input-field-separator] {
-    display: inline-block;
-    width: 1px;
-    height: var(--fs-spacing-3);
-    margin: 0 var(--fs-spacing-0);
-    background-color: var(--fs-border-color-light, `#e0e0e0`);
-  }
packages/components/src/molecules/FileUploadCard/FileUploadCard.tsx (2)

256-269: ⚠️ Potential issue | 🔴 Critical

Default CSV template is malformed — column/row structure is broken.

The template string 'SKU,Quantity\nAB001,AB100,AB999\n2,5,49' has a 2-column header but 3-value data rows, and the data appears transposed (SKUs on one row, quantities on the next). Downloading this template will confuse users and fail the parser's own validation.

Proposed fix
-      const csvContent = 'SKU,Quantity\nAB001,AB100,AB999\n2,5,49'
+      const csvContent = 'SKU,Quantity\nAB001,2\nAB100,5\nAB999,49'

165-169: ⚠️ Potential issue | 🟠 Major

isValidFileType ignores the accept prop — hardcoded to .csv only.

The component accepts an accept prop (defaulting to '.csv'), but validation on lines 165-169 is hardcoded to ['.csv']. If a consumer passes accept=".csv,.xlsx", the input will allow .xlsx selection but validation will reject it.

Proposed fix: derive valid extensions from the accept prop
-  const isValidFileType = (file: File): boolean => {
-    const fileName = file.name.toLowerCase()
-    const validExtensions = ['.csv']
-    return validExtensions.some((ext) => fileName.endsWith(ext))
-  }
+  const isValidFileType = (file: File): boolean => {
+    const fileName = file.name.toLowerCase()
+    const validExtensions = accept.split(',').map((ext) => ext.trim().toLowerCase())
+    return validExtensions.some((ext) => fileName.endsWith(ext))
+  }
packages/core/src/components/search/SearchInput/SearchInput.tsx (1)

319-333: ⚠️ Potential issue | 🟠 Major

Props spread overrides all defaults instead of merging.

...(fileUploadCardProps ?? DEFAULT_FILE_UPLOAD_CARD_PROPS) uses an all-or-nothing fallback. If a consumer passes fileUploadCardProps={{ title: 'Custom' }}, all other defaults (errorMessages, getCompletedStatusText, etc.) are lost.

Spread defaults first, then overrides:

🔧 Suggested fix
-                  ...(fileUploadCardProps ?? DEFAULT_FILE_UPLOAD_CARD_PROPS),
+                  ...DEFAULT_FILE_UPLOAD_CARD_PROPS,
+                  ...fileUploadCardProps,
🤖 Fix all issues with AI agents
In `@packages/components/package.json`:
- Around line 37-39: Remove the unused dependency entry "@koale/useworker" from
packages/components/package.json and regenerate the lockfile (npm/yarn/pnpm
install) so the package is actually removed from the install tree; confirm there
are no imports of "@koale/useworker" by searching the repo and run the build to
ensure tree-shaking still preserves consumers who don't use CSV/upload, and keep
"papaparse" and "react-dropzone" as they are used by useCSVParser.ts and
Dropzone.tsx respectively.

In `@packages/components/src/hooks/useCSVParser.ts`:
- Line 38: The default parameter options: CSVParserOptions = {} creates a new
object each render and invalidates the useCallback onParseFile; change the
signature of useCSVParser to accept options without a fresh-object default
(e.g., no default or options?: CSVParserOptions) and inside the hook create a
stable mergedOptions using a stable reference (either a top-level defaultOptions
constant or useMemo(() => ({ ...defaultOptions, ...options }), [options])) and
then use mergedOptions in the dependency array for onParseFile so the callback
is not recreated every render.
- Around line 126-135: The findColumnIndex function currently uses
header.toLowerCase().trim().includes(name.toLowerCase()) which can yield false
positives (e.g., "description" matching "id"); change the matching logic in
findColumnIndex to use either exact token equality or a case-insensitive
word-boundary regex: normalize header and name (trim, toLowerCase()), split
header into tokens or apply new RegExp(`\\b${escapeRegex(name)}\\b`, 'i') and
test against the header, falling back to exact equality rather than substring
includes; ensure you update references to headers and columnNames accordingly
and keep the comparison case-insensitive.
- Around line 225-228: The code mutates PapaParse's chunk object by assigning
results.data = results.data.slice(1); instead, create a local variable (e.g.,
const rows = results.data.slice(1)) and use that local variable for further
processing, remove the in-place assignment to results.data and stop mutating the
parser's result object; update subsequent uses (within the parse chunk handling
in useCSVParser / the header-processing branch where isHeaderProcessed is set)
to reference the new local variable and tighten types to avoid any.

In `@packages/components/src/hooks/useFileUpload.ts`:
- Around line 32-45: In useFileUpload's switch on `code`, wrap the `case
'file-too-large':` body in braces to scope `const sizeMB` (preventing the Biome
lint leak), and update the 'file-invalid-type' message to match actual allowed
types by referencing `DEFAULT_FILE_UPLOAD_OPTIONS.acceptedTypes` (or explicitly
state "CSV only" if that constant only contains CSV); keep the `too-many-files`
message using `options.maxFiles` as-is. Ensure you modify the switch in
useFileUpload to use a braced block for the sizeMB calculation and change the
invalid-type string to the correct accepted-types wording.
- Line 27: The hook useFileUpload currently replaces defaults when called with a
partial options object, causing fields like maxSize and maxFiles to become
undefined; fix this by shallow-merging options with DEFAULT_FILE_UPLOAD_OPTIONS
(e.g., const resolvedOptions = { ...DEFAULT_FILE_UPLOAD_OPTIONS, ...options })
and use resolvedOptions throughout the function (including in the error message
logic where Math.round(...) is used) so maxSize/maxFiles never become undefined,
and update any useCallback/useMemo dependency arrays to reference
resolvedOptions (or its individual props) instead of the raw options object.

In `@packages/components/src/index.ts`:
- Around line 3-7: The package root currently re-exports CSV parser types but
not FileUploadOptions or DEFAULT_FILE_UPLOAD_OPTIONS; update the exports so
consumers can import the file-upload types/constants from the package root by
adding exports for FileUploadOptions and DEFAULT_FILE_UPLOAD_OPTIONS from
'./hooks/useFileUpload' (alongside the existing useFileUpload re-export in
hooks/index.ts) so that FileUploadOptions and DEFAULT_FILE_UPLOAD_OPTIONS are
available at the package level.

In `@packages/components/src/molecules/Dropzone/Dropzone.tsx`:
- Around line 55-66: Update the JSDoc for the Dropzone props to accurately
describe their behavior: change the comment for noClick to indicate it disables
opening the file dialog on click, change noKeyboard to indicate it disables
opening the file dialog via keyboard, and change noDrag to indicate it disables
drag-and-drop on the drop zone; update these comments next to the noClick,
noKeyboard, and noDrag prop definitions in the Dropzone component so public API
docs reflect the correct semantics.
- Around line 141-151: The component currently spreads {...getRootProps()}
before {...otherProps}, which lets consumer props override critical handlers; in
the Dropzone component swap the spreads so consumer props are applied first and
then {...getRootProps()} is spread last (keep ref, data-attributes and testId in
place) to ensure getRootProps() handlers (onClick, onKeyDown, onDragEnter, etc.)
cannot be silently overridden by otherProps.

In `@packages/components/src/molecules/FileUploadCard/FileUploadCard.tsx`:
- Around line 131-134: The FileUploadCard props currently destructure
formatterFileSize and hasError but never use them; remove these dead props from
the public API by deleting formatterFileSize and hasError from the props
destructuring and from the component's Props/interface/type definition (and any
default values), keeping only formatterFileName and isUploading (and other used
props), or alternatively, if you intend to preserve behavior, implement their
logic inside FileUploadCard where formatterFileSize should be used to format
displayed size and hasError should drive error UI; prefer removing the unused
props to avoid breaking the public surface if no behavior is required.

In `@packages/core/src/components/search/fileUploadCardDefaults.ts`:
- Around line 15-42: Import the FileUploadErrorType enum and add a type
annotation to the errorMessages export to ensure keys align with the enum (e.g.,
type errorMessages as Record<FileUploadErrorType, { title: string; description:
string }>), then update the declaration of errorMessages in
fileUploadCardDefaults.ts so TypeScript will flag any missing or mismatched
keys; keep the existing object values but ensure the type is applied to the
errorMessages symbol.

In `@packages/core/src/components/search/SearchInput/SearchInput.tsx`:
- Around line 216-237: In handleSearch, remove the two Portuguese console.log
debug statements that print when csvData is null and after building payload;
either delete them or replace with a properly gated debug/logger call (e.g., use
an environment check or a centralized logger) so no informal/Portuguese console
output ships to production; locate handleSearch in SearchInput.tsx and update
the code around the payload creation and window.dispatchEvent call accordingly.
- Line 132: The state variable isUploadModalOpen is never set to true so
UploadFileDropdown never mounts; either wire setIsUploadModalOpen(true) to the
user action that should open the upload UI (for example call
setIsUploadModalOpen(true) from the attachment button's onClick/handler
currently rendering the attachment icon) ensuring the dynamically imported
UploadFileDropdown (imported in lines around the dynamic import) and the
conditional JSX that checks isUploadModalOpen are reachable, or remove the
dynamic import and the conditional render block for UploadFileDropdown to
eliminate dead code; update any handler names you use (e.g., the attachment
button click handler) and ensure you still call setIsUploadModalOpen(false) to
close the modal.
- Around line 320-332: Remove the unsafe type assertion "as FileUploadCardProps"
on the FileUploadCard props spread and let TypeScript validate the prop object;
update the constructed props (isOpen, onDismiss/handleDismiss,
onFileSelect/handleFileSelect, onDownloadTemplate/handleDownloadTemplate,
formatterFileSize/formatFileSize, formatterFileName/formatFileName,
onSearch/handleSearch, isUploading/isCsvProcessing, hasError/!!csvError and the
spread of fileUploadCardProps ?? DEFAULT_FILE_UPLOAD_CARD_PROPS) so they exactly
match the FileUploadCardProps shape (add any missing required props, correct
types, or adjust DEFAULT_FILE_UPLOAD_CARD_PROPS), fix any resulting type errors
in those helpers or default props, and re-run the type checker to ensure no
assertion is needed.

In
`@packages/core/src/components/search/UploadFileDropdown/UploadFileDropdown.tsx`:
- Around line 141-146: handleUseData currently just logs parsed CSV rows
(csvData.data) and must not leak data; replace the TODO/console.log by invoking
a prop callback or actual processing: add an optional prop like onUseData
(signature: (data: ParsedCsvType) => void) to the UploadFileDropdown component
and call onUseData(csvData.data) inside handleUseData (guarding existence), or
implement the real handling logic here if this component should process/upload
the data itself; remove the console.log and TODO placeholder and ensure
handleUseData returns/updates state or triggers the callback accordingly.
- Around line 47-64: The component UploadFileDropdown currently shows the same
CSV parsing error twice: once via the useEffect that calls pushToast when
csvError is set and again via the inline error JSX that renders csvError; remove
the duplicate by deleting (or conditionally hiding) the inline error block that
directly renders csvError (the JSX that displays the inline banner) so only the
toast produced in the useEffect remains, or alternatively remove the useEffect
pushToast and keep the inline banner—ensure you only keep one presentation path
for csvError and update references to csvError accordingly.

In `@packages/core/src/utils/utilities.ts`:
- Around line 148-156: formatFileSize currently produces NaN for negative inputs
and undefined for sizes >= 1 TB; update the formatFileSize function to (1) guard
against negative bytes by normalizing negative inputs to 0 or returning "0
Bytes" for bytes <= 0, (2) extend the sizes array to include larger units (e.g.,
'TB', 'PB', ...) or clamp the computed index i to the last available unit, and
(3) ensure i is bounded (Math.max(0, Math.min(i, sizes.length - 1))) before
indexing sizes to avoid undefined; modify the function body (formatFileSize,
sizes array, and the computation of i) accordingly.

In `@packages/ui/package.json`:
- Line 57: The peerDependency entry "@faststore/components": "workspace:*" in
packages/ui/package.json pins the published package to an exact dev version;
update the peerDependencies entry to use "workspace:^" (i.e.,
"@faststore/components": "workspace:^") so the published package resolves to a
caret range (e.g., ^3.97.0-dev.2) and allows compatible minor/patch updates for
consumers.

In `@packages/ui/src/components/molecules/Dropzone/styles.scss`:
- Around line 69-78: The selector &[data-fs-dropzone-disabled="true"] currently
sets pointer-events: none which prevents the cursor: not-allowed and nested
&:hover from ever taking effect; remove the pointer-events: none declaration
from that rule so the not-allowed cursor and &:hover background/border styles
are visible, and ensure disabling is enforced elsewhere (e.g., using
aria-disabled and JS guards in the Dropzone component's event handlers) rather
than via CSS pointer suppression; keep the cursor: not-allowed and &:hover
blocks under &[data-fs-dropzone-disabled="true"] and update the Dropzone logic
to ignore pointer interactions when disabled.

In `@packages/ui/src/components/molecules/SearchInputField/styles.scss`:
- Around line 68-72: The transition for box-shadow in the SearchInputField
styles uses the timing variable in both the duration and the timing-function
slots; update the transition declaration so box-shadow uses
var(--fs-search-input-field-transition-timing) for the duration and
var(--fs-search-input-field-transition-function) for the timing function (i.e.,
replace the second var used for box-shadow with
--fs-search-input-field-transition-function) to match the border transition and
produce a valid transition.
🧹 Nitpick comments (21)
packages/ui/src/components/molecules/SearchInputField/styles.scss (2)

104-104: Hardcoded fallback color #e0e0e0.

Consider extracting this into a design token (e.g., --fs-search-input-field-separator-color) so it stays in sync with the rest of the theming system rather than relying on a magic hex value.


107-113: Upload button uses a fixed right offset — fragile coupling.

The right: 2.5rem / right: 3rem values are magic numbers tightly coupled to the sibling button widths. If the search button or attachment button sizes change, this will break. Consider using flexbox ordering or logical positioning within the actions container instead of absolute offsets.

packages/ui/src/styles/components.scss (1)

67-67: Dropzone import breaks alphabetical ordering of Molecules section.

All other molecule imports are sorted alphabetically. Dropzone should be placed between Dropdown (line 31) and FileUploadCard (line 32).

Suggested fix
 `@import` "../components/molecules/Dropdown/styles";
+@import "../components/molecules/Dropzone/styles";
 `@import` "../components/molecules/FileUploadCard/styles";

And remove line 67.

packages/ui/src/components/molecules/FileUploadCard/styles.scss (1)

18-19: Hardcoded hex colors should use design tokens for consistency and theming.

Lines 18-19 introduce #0366dd and #bfdbfe as raw hex values. Similar hardcoded colors appear throughout this file (#ffffff at line 160, #f0f7ff at line 105, #d6d6d6 at line 193, #f5f5f5 at line 230). The rest of the design system uses var(--fs-*) tokens. These should follow the same pattern to support theming and dark mode.

packages/ui/src/components/molecules/Dropzone/styles.scss (2)

7-7: Misleading CSS custom property name --fs-modal-bg-color.

This token is named modal but scoped to the Dropzone component. Consider renaming to --fs-dropzone-bg-color for clarity and to avoid confusion with any actual modal tokens.


25-25: Prefer explicit transition properties over transition: all.

transition: all can cause unintended transitions on properties you didn't mean to animate and has a minor performance cost. Specify only the properties that should transition (e.g., background-color, border-color, transform).

packages/core/src/components/search/UploadFileDropdown/section.module.scss (1)

77-92: Duplicated magic number 500px for max-width.

Both [data-fs-upload-result-file-name] and [data-fs-upload-result-file-rows] share the same max-width: 500px along with identical overflow/ellipsis styles. Consider extracting to a local CSS variable (e.g., --fs-upload-result-max-width: 500px) or a shared mixin.

packages/components/src/molecules/FileUploadStatus/FileUploadStatus.tsx (2)

180-194: getErrorMessage() is called twice, creating redundant objects.

On lines 183 and 186, getErrorMessage() is invoked separately for title and description. Destructure once before rendering:

♻️ Proposed fix
           {state === FileUploadState.Error ? (
-            <>
-              <p data-fs-file-upload-status-text-error>
-                {getErrorMessage().title}
-              </p>
-              <p data-fs-file-upload-status-text-error>
-                {getErrorMessage().description}
-              </p>
-            </>
+            (() => {
+              const { title, description } = getErrorMessage()
+              return (
+                <>
+                  <p data-fs-file-upload-status-text-error>{title}</p>
+                  <p data-fs-file-upload-status-text-error>{description}</p>
+                </>
+              )
+            })()

Alternatively, compute it outside the JSX at the top of the component body:

const errorInfo = state === FileUploadState.Error ? getErrorMessage() : null

Then reference errorInfo.title / errorInfo.description in JSX.


166-172: Consider adding a live region for accessibility.

Upload status transitions (uploading → completed, or → error) won't be announced to screen readers. Adding role="status" (for non-urgent updates) or role="alert" (for errors) and aria-live="polite" on the container would improve the experience for assistive technology users.

♿ Proposed enhancement
     <div
       data-fs-file-upload-status
       data-fs-file-upload-status-state={state}
       data-testid={testId}
+      role={state === FileUploadState.Error ? 'alert' : 'status'}
+      aria-live="polite"
       {...otherProps}
     >
packages/ui/src/components/molecules/FileUploadStatus/styles.scss (2)

13-15: Hardcoded hex colors break the design token system.

Multiple hex values are used instead of design tokens: #08a822, #d31a15 (lines 13-15), #fdf6f5/#ffdfd9 (lines 47-48), #0366dd/#0252b8 (lines 197-199, 219-225), and #d6d6d6 (line 224). This undermines theme consistency and dark-mode support. Replace with appropriate --fs-color-* tokens or define new component-level tokens at the top of the block.

Also applies to: 47-48, 197-199, 219-225


29-29: Remove commented-out code.

// height: 188px; is dead code. Remove it to keep the stylesheet clean.

packages/core/src/components/search/fileUploadCardDefaults.ts (1)

13-14: getCompletedStatusText always formats as KB.

For files larger than ~1 MB, displaying 1024.0 KB instead of 1.0 MB is awkward. Consider a simple size formatter that adapts the unit. This is minor since CSV files for quick order are likely small.

packages/core/src/components/sections/Navbar/Navbar.tsx (2)

116-120: Shallow fallback — partial CMS overrides won't merge with defaults.

The nullish coalescing (??) provides an all-or-nothing fallback. If CMS supplies a partial fileUploadCardProps (e.g., only title), the defaults won't fill in the missing fields — downstream components will receive undefined for the omitted labels.

Since this is temporary (per the TODO in fileUploadCardDefaults.ts), this is fine for now, but when CMS integration lands, consider a shallow merge:

♻️ Future-proof merge approach
         searchInput={{
           ...searchInput,
-          fileUploadCardProps:
-            searchInput.fileUploadCardProps ?? DEFAULT_FILE_UPLOAD_CARD_PROPS,
+          fileUploadCardProps: {
+            ...DEFAULT_FILE_UPLOAD_CARD_PROPS,
+            ...searchInput.fileUploadCardProps,
+          },
         }}

This ensures any CMS-provided fields override the defaults while missing fields retain safe fallback values.


44-58: fileUploadCardProps type uses Partial<Record<string, ...>> for errorMessages but downstream expects FileUploadErrorType keys.

The errorMessages field is typed as Partial<Record<string, { title: string; description: string }>> (line 55-57) — this accepts arbitrary string keys. Downstream, FileUploadStatusProps.errorMessages expects Partial<Record<FileUploadErrorType, ...>>. Consider aligning the type here with FileUploadErrorType for compile-time safety.

packages/components/src/molecules/Dropzone/Dropzone.tsx (1)

167-189: Hardcoded accepted/rejected file lists look like debug UI.

The raw file.name - file.size bytes and error lists are rendered unconditionally inside the dropzone. This doesn't seem intended for production — consumers would typically handle display themselves via callbacks. Consider removing these or making them opt-in via a prop (e.g., showFileList).

packages/components/src/molecules/FileUploadCard/FileUploadCard.tsx (1)

171-200: handleFileChange and handleDrop duplicate nearly identical validation/upload logic.

Both handlers perform the same sequence: set file → validate type → set upload state → optionally simulate → call onFileSelect. Extract a shared helper to reduce the duplication and the risk of the two paths diverging.

Also applies to: 213-246

packages/components/src/hooks/useCSVParser.ts (1)

168-171: Silent error pruning drops diagnostic data.

When errors exceed 1000, errors.splice(0, 500) discards the oldest 500 without any indication. A user parsing a large malformed file would lose the initial (most useful) error context. Consider keeping the first N errors and discarding later ones instead.

Proposed fix
         // Limit errors to avoid excessive memory usage
-        if (errors.length > 1000) {
-          errors.splice(0, 500)
-        }
+        if (errors.length >= 1000) {
+          return null  // Stop collecting, first errors are most useful
+        }
packages/core/src/components/search/UploadFileDropdown/UploadFileDropdown.tsx (2)

24-231: Significant logic duplication with SearchInput.tsx.

This component reimplements CSV parsing, template download, and error handling that SearchInput.tsx also sets up (lines 148–237 there). Both call useCSVParser, build a blob for template download, and manage csvData state independently. Consider extracting the shared CSV workflow into a single custom hook (e.g., useCSVUploadFlow) to keep the logic DRY and avoid divergence.


148-231: No props = zero configurability and no i18n support.

All user-facing strings ("Search", "Drop a file to search in bulk", "Download Template", "products found", etc.) are hard-coded. SearchInput.tsx already defines a fileUploadCardProps shape for CMS-editable copy and uses DEFAULT_FILE_UPLOAD_CARD_PROPS. This component should follow the same pattern, accepting text props so copy can be localized or edited from CMS.

packages/core/src/components/search/SearchInput/SearchInput.tsx (2)

130-142: Five overlapping boolean/state variables manage upload visibility — simplify.

isUploadModalOpen, fileUploadVisible, isUploadOpen, hasFile, plus csvData all partially control what's shown. This makes the state machine hard to reason about and prone to inconsistencies (e.g., isUploadOpen || hasFile || fileUploadVisible on line 322).

Consider consolidating into a single discriminated state, e.g.:

type UploadUIState = 'closed' | 'dropzone' | 'processing' | 'results'
const [uploadState, setUploadState] = useState<UploadUIState>('closed')

This would eliminate the combinatorial explosion and make transitions explicit.


48-55: Dynamic import resolves .default but the barrel already re-exports default.

The .then((mod) => mod.default) on line 53 is necessary here because next/dynamic expects a component, but note that the barrel index.ts does export { default }. This works, but if the barrel ever switches to a named export, this will silently break at runtime. A named export in the barrel + named import in dynamic would be more resilient.

Copy link

@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: 13

Caution

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

⚠️ Outside diff range comments (4)
packages/components/src/molecules/SearchInputField/SearchInputField.tsx (1)

123-126: ⚠️ Potential issue | 🟡 Minor

Both aria-label props on icon buttons are optional with no fallback.

attachmentButtonAriaLabel and submitButtonAriaLabel can be undefined, which would render <IconButton> elements without accessible names — a WCAG violation. Consider providing sensible defaults (e.g., "Attach file" and "Submit search").

This predates this PR but is now codified in the public interface, making it a good time to address it.

Also applies to: 136-139

packages/components/src/molecules/FileUploadCard/FileUploadCard.tsx (2)

256-269: ⚠️ Potential issue | 🔴 Critical

Default CSV template content is malformed.

Line 261: The header declares 2 columns (SKU,Quantity) but each "data row" has 3 comma-separated values. This produces a broken CSV that will confuse users and likely fail validation when re-uploaded.

Additionally, the data layout seems transposed — it looks like columns were written as rows. A corrected version might be:

Proposed fix (assuming 2-column SKU+Quantity template)
-      const csvContent = 'SKU,Quantity\nAB001,AB100,AB999\n2,5,49'
+      const csvContent = 'SKU,Quantity\nAB001,2\nAB100,5\nAB999,49'

165-169: ⚠️ Potential issue | 🟠 Major

isValidFileType hardcodes .csv and ignores the accept prop.

The accept prop defaults to '.csv' but can be overridden by consumers. Yet validation always checks only ['.csv'], so passing accept=".xlsx" would accept the file in the native picker but then immediately reject it in your validation logic.

Derive the valid extensions from the accept prop instead:

Proposed fix
   const isValidFileType = (file: File): boolean => {
     const fileName = file.name.toLowerCase()
-    const validExtensions = ['.csv']
+    const validExtensions = accept
+      .split(',')
+      .map((ext) => ext.trim().toLowerCase())
+      .filter((ext) => ext.startsWith('.'))
     return validExtensions.some((ext) => fileName.endsWith(ext))
   }
packages/core/src/components/search/SearchInput/SearchInput.tsx (1)

319-334: ⚠️ Potential issue | 🔴 Critical

Default props are replaced, not merged — partial fileUploadCardProps drops all other defaults.

...(fileUploadCardProps ?? DEFAULT_FILE_UPLOAD_CARD_PROPS) uses nullish coalescing: if fileUploadCardProps is provided (even with a single field like { title: 'Custom' }), the entire DEFAULT_FILE_UPLOAD_CARD_PROPS is skipped. Required props like fileInputAriaLabel, dropzoneAriaLabel, errorMessages, etc. will be undefined, likely causing runtime errors or broken UI.

Merge defaults first, then override with user-provided props.

Suggested fix
               {...({
                 isOpen: isUploadOpen || hasFile || fileUploadVisible,
                 onDismiss: handleDismiss,
                 onFileSelect: handleFileSelect,
                 onDownloadTemplate: handleDownloadTemplate,
                 formatterFileSize: formatFileSize,
                 formatterFileName: formatFileName,
                 onSearch: handleSearch,
                 isUploading: isCsvProcessing,
                 hasError: !!csvError,
-                ...(fileUploadCardProps ?? DEFAULT_FILE_UPLOAD_CARD_PROPS),
+                ...DEFAULT_FILE_UPLOAD_CARD_PROPS,
+                ...fileUploadCardProps,
               } as FileUploadCardProps)}
🤖 Fix all issues with AI agents
In `@packages/components/src/molecules/Dropzone/Dropzone.tsx`:
- Around line 141-191: The Dropzone root <div> in the Dropzone component is
missing an accessible role/label; update the component (Dropzone) to accept an
ariaLabel prop (or derive a default) and apply a suitable role (e.g.,
role="button" or role="region") and aria-label on the root element that uses
ref/getRootProps (the element with data-fs-dropzone and {...getRootProps()});
ensure the ariaLabel prop is passed through otherProps or merged into
getRootProps so screen readers can identify the dropzone, and only add the
role/aria-label when not disabled to avoid misleading semantics.
- Around line 141-152: The forwarded ref passed into the Dropzone component is
being overwritten by the ref returned inside getRootProps() because
{...getRootProps()} is spread after ref={ref}; update the Dropzone to merge the
consumer ref and the internal ref from getRootProps() (e.g., via a mergeRefs or
useMergeRefs utility) and pass that merged callback as the effective ref on the
root div so both the component internals and the forwarded ref are honored;
reference the Dropzone component, the getRootProps() call, and the ref prop when
applying the merged ref.

In `@packages/core/CHANGELOG.md`:
- Around line 6-12: The changelog entries for useCSVParser, useFileUpload, and
FileUploadCard are recorded under the wrong package; update the CHANGELOG so
these items appear in the components package changelog (or explicitly note in
core that these are re-exports). Move the three bullet points (useCSVParser,
useFileUpload, FileUploadCard) out of the core changelog and add them to the
components changelog, or if core truly re-exports them, keep concise entries in
core that reference the components changelog and mention the re-export; ensure
the symbols useCSVParser, useFileUpload, and FileUploadCard are mentioned
exactly as in the diff for discoverability.

In `@packages/core/src/components/search/SearchInput/SearchInput.tsx`:
- Around line 150-159: The inline options object passed to useCSVParser ({
delimiter: ',', skipEmptyLines: true }) creates a new reference each render
which invalidates onParseFile; fix this by extracting the options into a stable
reference — either declare a module-level constant (e.g. CSV_PARSER_OPTIONS) or
wrap the object in useMemo and pass that memoized variable to useCSVParser,
ensuring useCSVParser(...) receives a stable options reference so onParseFile
(and related callbacks) remain memoized.
- Around line 210-214: handleDismiss currently clears csvData, hides the upload
UI and calls onClearError but fails to reset the upload-state flags (hasFile and
isUploadOpen), causing isOpen to remain true on reopen; update handleDismiss to
also call setHasFile(false) and setIsUploadOpen(false) (in addition to
setCsvData(null), setFileUploadVisible(false), and onClearError()) so the upload
card opens in a clean initial state.

In `@packages/core/src/components/search/UploadFileDropdown/section.module.scss`:
- Around line 1-2: The `@import` is nested inside the .section selector and uses a
.scss extension, which triggers Stylelint errors; move the import statement
(currently "@import
\"@faststore/ui/src/components/molecules/Dropzone/styles.scss\";") to the
top-level of the module (before any selectors) and drop the .scss extension so
it reads `@import` "@faststore/ui/src/components/molecules/Dropzone/styles"; if
you intentionally needed those Dropzone rules scoped under .section instead,
replace the import approach by using `@use` with a namespace and then reference
the tokens, or copy/override only the required styles inside the .section block
to preserve scoping.

In
`@packages/core/src/components/search/UploadFileDropdown/UploadFileDropdown.tsx`:
- Around line 111-139: handleDownloadTemplate currently calls a.click() on a
detached anchor (which can fail in some browsers like Firefox) and relies on the
try/catch even though onGenerateTemplate swallows errors and returns null;
update handleDownloadTemplate to explicitly check for a falsy csvContent and
show the error toast in that branch, and when creating the anchor element append
it to document.body before calling a.click(), then revokeObjectURL and remove
the anchor after the download; reference the handleDownloadTemplate function,
the onGenerateTemplate call, and pushToast for where to show the error toast.
- Around line 35-40: The inline options object passed to useCSVParser
(containing delimiter, skipEmptyLines, skuColumnNames, quantityColumnNames)
creates a new reference each render which makes the hook's onParseFile unstable;
fix this by hoisting that options object to a module-level constant or memoizing
it with useMemo (e.g., const csvOptions = useMemo(() => ({ delimiter: ',',
skipEmptyLines: true, skuColumnNames: [...], quantityColumnNames: [...] }), []))
and pass csvOptions to useCSVParser so onParseFile becomes stable.

In `@packages/core/src/utils/utilities.ts`:
- Around line 120-139: The current formatFileName logic early-returns based on
total fileName length but only truncates nameWithoutExtension, which allows long
extensions to exceed maxLength; update formatFileName to ensure the final
returned string does not exceed maxLength by accounting for extension length:
compute availableBase = maxLength - extension.length, if availableBase <= 0
return a truncated extension-aware placeholder (e.g., slice from the end of the
extension), otherwise truncate nameWithoutExtension to availableBase using the
existing start/.../end approach (use maxLength, extensionIndex, extension,
nameWithoutExtension variables) and then return
`${truncatedNameWithoutExtension}${extension}` so the overall length is capped
at maxLength.

In `@packages/ui/src/components/molecules/Dropzone/styles.scss`:
- Around line 178-188: Replace the deprecated CSS property `clip` on the file
input selector (`input[type="file"]`) with a modern `clip-path` value: remove
`clip: rect(0, 0, 0, 0);` and add a `clip-path: inset(50%);` (or `clip-path:
inset(0 0 0 0 round 0)` if you prefer explicit zero inset) to achieve the same
visual hiding; keep the existing positioning, sizing, overflow and
accessibility-related rules intact so `input[type="file"]` remains visually
hidden but focusable.
- Around line 192-207: Replace the old max-width media feature with modern range
notation: change the media query declaration from "@media (max-width: 768px)" to
the range syntax "@media (width <= 768px)" and keep the inner block for
[data-fs-dropzone], [data-fs-dropzone-icon] svg, and [data-fs-dropzone-text]
unchanged so the same styles apply under the new media query.

In `@packages/ui/src/components/molecules/SearchInputField/styles.scss`:
- Around line 107-113: The upload button selector (&[data-fs-search-input-field]
[data-fs-search-input-field-upload-button]) uses the physical property "right"
which breaks RTL; change both occurrences (base and inside the `@include`
media(">=notebook") block) to the logical property "inset-inline-end" so
positioning respects text direction and matches the other rules in this
stylesheet.
- Around line 79-105: Remove the duplicated CSS block that re-declares the
selectors [data-fs-icon-button][data-fs-button-variant],
[data-fs-search-input-field-attachment-button] (and its nested [data-fs-icon] +
&:hover rule) and [data-fs-search-input-field-separator] — keep only the
existing definitions (the later set) to avoid emitting duplicate styles; delete
the earlier duplicate block that includes the media query adjustments,
attachment button transform/hover, and separator styles so only one set of those
selectors remains.
🧹 Nitpick comments (11)
packages/ui/src/components/molecules/SearchInputField/styles.scss (1)

99-105: Consider using a design token instead of hardcoded #e0e0e0 fallback.

The separator's background-color falls back to a hardcoded hex value. If this color is used elsewhere or if the theme changes, it won't update. If a suitable token exists (or can be added), prefer that for consistency.

packages/ui/src/components/molecules/FileUploadCard/styles.scss (1)

13-13: Consider replacing hardcoded hex colors with design tokens.

Multiple hex values (#0366dd, #0252b8, #d6d6d6, #bfdbfe, #ffffff) are scattered throughout this file and FileUploadStatus/styles.scss. Extracting these into CSS custom properties (design tokens) at the component root would improve theming consistency and maintainability. Not a blocker — can be addressed in a follow-up.

Also applies to: 18-18, 19-19, 160-160, 188-188, 189-189, 191-191, 192-192, 193-193, 194-194, 195-195, 207-207

packages/components/src/index.ts (1)

431-432: Dropzone export is placed after the Organisms section.

All other molecule exports live between the // Molecules and // Organisms comments. This placement at the very end breaks the established file organization.

Suggested move

Move these two lines into the Molecules section (e.g., after the existing Dropdown exports around line 107, alphabetically).

packages/ui/src/components/molecules/Dropzone/styles.scss (1)

7-7: Design token --fs-modal-bg-color appears copy-pasted from Modal.

This variable name is misleading in a Dropzone context. Consider renaming to --fs-dropzone-bg-color for clarity and to avoid accidental coupling with Modal tokens.

packages/components/src/molecules/FileUploadStatus/FileUploadStatus.tsx (1)

180-188: getErrorMessage() is called twice per render in error state.

Each call re-computes the same lookup. Store the result once:

Proposed fix
          {state === FileUploadState.Error ? (
-           <>
-             <p data-fs-file-upload-status-text-error>
-               {getErrorMessage().title}
-             </p>
-             <p data-fs-file-upload-status-text-error>
-               {getErrorMessage().description}
-             </p>
-           </>
+           (() => {
+             const { title, description } = getErrorMessage()
+             return (
+               <>
+                 <p data-fs-file-upload-status-text-error>{title}</p>
+                 <p data-fs-file-upload-status-text-error>{description}</p>
+               </>
+             )
+           })()

Or extract to a variable before the return statement.

packages/components/src/molecules/FileUploadCard/FileUploadCard.tsx (1)

171-199: Duplicated file handling logic between handleFileChange and handleDrop.

The validation → set state → simulate upload → callback sequence is nearly identical in both handlers. Extract a shared processFile(files: File[]) helper to reduce duplication and ensure future changes apply consistently.

Also applies to: 213-246

packages/components/src/hooks/useCSVParser.ts (1)

168-171: Error truncation discards the earliest diagnostics.

When errors exceed 1000, errors.splice(0, 500) drops the first 500 entries — the rows most likely near the file header. A simple length cap (stop pushing after N) would be more predictable and avoids repeated O(n) splices.

Proposed fix
-        // Limit errors to avoid excessive memory usage
-        if (errors.length > 1000) {
-          errors.splice(0, 500)
-        }
+        // Cap collected errors to avoid excessive memory usage
+        if (errors.length < 1000) {
+          errors.push(`Row ${rowIndex + 2}: ${errorMessage}`)
+        }

Move the push inside the guard and remove the outer push on line 166.

packages/core/src/components/search/UploadFileDropdown/UploadFileDropdown.tsx (1)

24-24: Component accepts no props — limits reusability and CMS integration.

UploadFileDropdown is a fully self-contained component with no configurable text, callbacks, or options. The Navbar/SearchInput integration passes fileUploadCardProps for label customization, but this component ignores them entirely — all strings are hardcoded. Consider accepting at least an onUseData callback and text props to align with the CMS-driven approach used elsewhere in this PR.

packages/core/src/components/search/SearchInput/SearchInput.tsx (3)

131-142: Five overlapping boolean states for one upload flow is hard to reason about.

isUploadModalOpen, fileUploadVisible, isUploadOpen, and hasFile all govern visibility/state of the upload UI. Consider consolidating into a single state machine or at minimum a reducer, e.g.:

type UploadState = 'idle' | 'dropzone' | 'uploading' | 'completed'
const [uploadState, setUploadState] = useState<UploadState>('idle')

This would eliminate the stale-state bugs (like the handleDismiss issue above) by design.


174-190: Event handlers are recreated every render — wrap with useCallback.

handleFileSelect, handleDownloadTemplate, handleDismiss, and handleSearch are all plain closures recreated on each render. Since they're passed as props to FileUploadCard, every parent re-render forces the child to re-render too.

Wrap each in useCallback with appropriate dependency arrays. For example:

const handleFileSelect = useCallback(async (files: File[]) => {
  // ...
}, [onClearError, onParseFile])

As per coding guidelines, packages/**/src/components/**: "Check rendering performance … use memo/useMemo/useCallback when appropriate."

Also applies to: 192-208, 216-237


83-97: errorMessages uses Record<string, …> instead of FileUploadErrorType — loses type safety.

The FileUploadCardProps interface in the components package types errorMessages as Partial<Record<FileUploadErrorType, …>>. Using string here means CMS integrators won't get autocompletion or compile-time checks for valid error keys like 'unsupported', 'empty', 'too-large', etc.

Consider importing and reusing FileUploadErrorType:

+import type { FileUploadErrorType } from '@faststore/ui'
 // ...
-    errorMessages?: Partial<
-      Record<string, { title: string; description: string }>
-    >
+    errorMessages?: Partial<
+      Record<FileUploadErrorType, { title: string; description: string }>
+    >

Copy link

@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: 5

Caution

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

⚠️ Outside diff range comments (1)
packages/core/src/components/search/SearchInput/SearchInput.tsx (1)

331-345: ⚠️ Potential issue | 🟠 Major

Spread order lets CMS props silently override event handlers.

The CMS prop object is spread after the explicit handler props (onDismiss, onFileSelect, onSearch, etc.). If fileUploadCardProps ever contains keys like onDismiss or onSearch, they will overwrite the functional handlers defined above. Spread the CMS labels first, then the handlers.

Also, the as FileUploadCardProps assertion still suppresses type checking (previously flagged).

Proposed fix — reverse spread order
             <FileUploadCard
-                {...({
-                  isOpen: isUploadOpen || hasFile || fileUploadVisible,
-                  onDismiss: handleDismiss,
-                  onFileSelect: handleFileSelect,
-                  onDownloadTemplate: handleDownloadTemplate,
-                  formatterFileSize: formatFileSize,
-                  formatterFileName: formatFileName,
-                  onSearch: handleSearch,
-                  isUploading: isCsvProcessing,
-                  hasError: !!csvError,
-                  ...(fileUploadCardProps ?? DEFAULT_FILE_UPLOAD_CARD_PROPS),
-                } as FileUploadCardProps)}
+                {...(fileUploadCardProps ?? DEFAULT_FILE_UPLOAD_CARD_PROPS)}
+                isOpen={isUploadOpen || hasFile || fileUploadVisible}
+                onDismiss={handleDismiss}
+                onFileSelect={handleFileSelect}
+                onDownloadTemplate={handleDownloadTemplate}
+                formatterFileSize={formatFileSize}
+                formatterFileName={formatFileName}
+                onSearch={handleSearch}
+                isUploading={isCsvProcessing}
+                hasError={!!csvError}
             />

This also removes the need for the as FileUploadCardProps assertion — TypeScript can now validate each prop individually.

🤖 Fix all issues with AI agents
In `@packages/components/src/molecules/Dropzone/Dropzone.tsx`:
- Around line 167-173: The dropzone doesn't expose its disabled state to
assistive tech; update the accessibilityProps logic so that when disabled is
true it sets aria-disabled (e.g., 'aria-disabled': true) and still provides the
role and aria-label fallback (ariaLabel ?? 'Drag and drop files here, or click
to select files'); change the current ternary that returns {} when disabled to
return an object with aria-disabled and the same role/aria-label, and apply the
same change to the other similar accessibilityProps usage (the block around the
second occurrence).

In `@packages/core/src/components/search/SearchInput/SearchInput.tsx`:
- Around line 202-218: The download may fail in Firefox because the anchor
created in handleDownloadTemplate is never attached to the DOM; modify
handleDownloadTemplate (which calls onGenerateTemplate) to append the created
anchor element to document.body before calling a.click(), then remove the anchor
from the DOM afterward (and still call window.URL.revokeObjectURL(url)); ensure
you create the Blob and url as before, set a.href/a.download, appendChild(a),
call a.click(), then removeChild(a) and revoke the url.

In
`@packages/core/src/components/search/UploadFileDropdown/UploadFileDropdown.tsx`:
- Around line 271-273: The Clear button wrapping the UIIcon in
UploadFileDropdown is missing an aria-label; update the component to pass an
accessible label to the Button (e.g., aria-label={labels.clearButtonAriaLabel})
and wire that label into the i18n labels interface and defaults by adding a
clearButtonAriaLabel key to UploadFileDropdownLabels (and any default
labels/translation objects) so consumers can localize it; ensure the Button
usage (function clearData and JSX with Button variant="tertiary" size="small")
reads the new label from props/labels.
- Around line 253-269: In UploadFileDropdown's render where csvData is shown,
remove the unreachable isProcessing conditional that would render <Loader />
(inside the JSX block that uses csvData, formatFileName, formatFileSize, and
labels.getCompletedStatusText) and always render
labels.getCompletedStatusText(formatFileSize(csvData.fileSize),
csvData.totalRows) instead; you can also remove the unused Loader reference in
that JSX branch since onParseFile guarantees isProcessing is false via its
finally block.

In `@packages/storybook/stories/upload-file-dropdown.stories.tsx`:
- Line 1: The import currently includes an unused symbol `useState` (import
React, { useState } from 'react') in the upload-file-dropdown stories; remove
`useState` from the import so it reads simply `import React from 'react'` (or
use the existing default import style) to eliminate the unused import warning
and keep the file clean.
🧹 Nitpick comments (4)
packages/components/src/molecules/Dropzone/Dropzone.tsx (2)

95-98: DropzoneState is exported but never produced by the component.

The component manages acceptedFiles and fileRejections internally and renders them, but doesn't expose this state to the parent (only onFilesAccepted/onFilesRejected callbacks fire). Consumers typing against DropzoneState won't be able to obtain a value of this type from the Dropzone itself. If this type is intended for consumers to build their own state management, consider adding a brief JSDoc note clarifying its purpose.


202-224: Built-in file/error lists may not suit most consumers.

The component unconditionally renders accepted files and rejection errors as simple <ul> lists. In most real-world usage (like the FileUploadCard in this PR), consumers will want full control over how files and errors are presented. Consider whether these default lists should be opt-in (e.g., via a showFileList prop) or removed in favor of render-prop/callback patterns, to avoid consumers needing to hide unwanted DOM with CSS.

packages/core/src/components/search/SearchInput/SearchInput.tsx (1)

334-334: isOpen is always truthy when FileUploadCard renders.

Inside {fileUploadVisible && ...}, fileUploadVisible is guaranteed true, making the || fileUploadVisible arm of isOpen: isUploadOpen || hasFile || fileUploadVisible redundant — isOpen can never be false here. If FileUploadCard needs an isOpen={false} state, the condition logic needs rethinking; otherwise simplify.

packages/core/src/components/search/UploadFileDropdown/section.module.scss (1)

77-92: Hardcoded max-width: 500px for file name and row details.

Consider using a design-token or relative unit instead of the hardcoded 500px so it adapts to different container widths (e.g., narrower viewports or a resized search dropdown).

Copy link

@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 (1)
packages/core/src/components/search/SearchInput/SearchInput.tsx (1)

331-345: ⚠️ Potential issue | 🟠 Major

as FileUploadCardProps type assertion suppresses type checking.

The cast on line 344 hides mismatches between the constructed object and FileUploadCardProps. If a required prop is added or renamed, TypeScript won't catch it. Remove the assertion and fix any resulting type errors.

Also note: the spread order ...(fileUploadCardProps ?? DEFAULT_FILE_UPLOAD_CARD_PROPS) comes after onSearch, isUploading, etc., so CMS-provided labels can accidentally overwrite functional props like onDismiss or onSearch if they share the same key.

♻️ Suggested fix for spread order
              <FileUploadCard
-                {...({
-                  isOpen: isUploadOpen || hasFile || fileUploadVisible,
-                  onDismiss: handleDismiss,
-                  onFileSelect: handleFileSelect,
-                  onDownloadTemplate: handleDownloadTemplate,
-                  formatterFileSize: formatFileSize,
-                  formatterFileName: formatFileName,
-                  onSearch: handleSearch,
-                  isUploading: isCsvProcessing,
-                  hasError: !!csvError,
-                  ...(fileUploadCardProps ?? DEFAULT_FILE_UPLOAD_CARD_PROPS),
-                } as FileUploadCardProps)}
+                {...(fileUploadCardProps ?? DEFAULT_FILE_UPLOAD_CARD_PROPS)}
+                isOpen={isUploadOpen || hasFile || fileUploadVisible}
+                onDismiss={handleDismiss}
+                onFileSelect={handleFileSelect}
+                onDownloadTemplate={handleDownloadTemplate}
+                formatterFileSize={formatFileSize}
+                formatterFileName={formatFileName}
+                onSearch={handleSearch}
+                isUploading={isCsvProcessing}
+                hasError={!!csvError}
               />

This ensures CMS labels are spread first and functional props can't be overridden. It also removes the need for the as cast.

🤖 Fix all issues with AI agents
In `@packages/components/src/molecules/Dropzone/Dropzone.tsx`:
- Around line 168-177: The accessibilityProps currently assigns role="button"
even when ariaLabel is missing, causing an unlabeled interactive element; update
the logic that builds accessibilityProps (referencing accessibilityProps,
ariaLabel and disabled in Dropzone.tsx) to provide a sensible fallback
accessible name when ariaLabel is falsy (for example derive a default like "Drop
files" or use an existing prop/children text) and set 'aria-label' to that
fallback so every role="button" instance always has an accessible name; ensure
both the disabled and non-disabled branches use the same fallback behavior.

In `@packages/core/src/components/search/SearchInput/SearchInput.tsx`:
- Around line 230-249: handleSearch dispatches a global CustomEvent
('faststore:file-search') with a payload and leaves a TODO; remove the ambiguous
public event or document its contract and complete integration: either (A)
delete the window.dispatchEvent call and the trailing TODO and rely solely on
the typed onFileSearch callback (onFileSearch and csvData.payload shape:
fileName, totalRows, fileSize, data), or (B) keep the CustomEvent but add a
documented exported type/JS Doc for the event detail shape and ensure listeners
are registered/cleaned up elsewhere; update handleSearch to reflect the chosen
approach and remove the TODO comment.
- Around line 202-220: handleDownloadTemplate currently only logs errors to
console and provides no user feedback; update it to surface failures the same
way UploadFileDropdown.tsx does by calling the shared toast/error UI when
onGenerateTemplate or the download process throws. In the catch block of
handleDownloadTemplate, replace or supplement console.error with the project
toast method (or set an error state) used elsewhere (e.g., the toast invocation
pattern from UploadFileDropdown.tsx) and include a clear message like "Failed to
download template" plus the error details so users see a visible failure
notification.

In `@packages/storybook/stories/upload-file-dropdown.stories.tsx`:
- Around line 2-9: Remove the unused Loader import from the named import list in
the stories file: locate the import statement that destructures Button,
Dropzone, Icon, Loader, SearchDropdown, UIProvider (the symbol "Loader" in that
list) and delete "Loader" from that destructuring so the module no longer
imports an unused symbol, keeping the rest of the imports unchanged.
🧹 Nitpick comments (5)
packages/storybook/stories/upload-file-dropdown.stories.tsx (3)

110-110: Consider using Storybook action() instead of console.log.

Using action('filesAccepted') from @storybook/addon-actions surfaces events in the Storybook Actions panel and avoids stray console noise.

Also applies to: 169-169


30-51: Duplicated interface may drift from source.

The comment on lines 22-29 explains the rationale — appreciated. Just flag that UploadFileDropdownLabels in @faststore/core is the source of truth (see UploadFileDropdown.tsx:26-70). If new label props are added there, this story won't surface them in the reference table until manually synced.

A // TODO: keep in sync with UploadFileDropdownLabels from @faststore/core`` or a lint/test that compares the two would reduce drift risk.


80-86: formatFileSize duplicated from core utilities.

This is identical to packages/core/src/utils/utilities.ts:160-168. Fine for a self-contained story, but worth a brief inline comment pointing to the canonical source so future maintainers know where to look if the logic changes.

packages/core/src/components/search/UploadFileDropdown/UploadFileDropdown.tsx (1)

161-174: Memoize handlers passed as props to UIDropzone.

handleFilesAccepted and handleFilesRejected are recreated every render and passed to UIDropzone via onFilesAccepted / onFilesRejected. This can cause unnecessary re-renders of the dropzone subtree. Wrap them with useCallback.

♻️ Suggested refactor
-  const handleFilesAccepted = async (files: File[]) => {
+  const handleFilesAccepted = useCallback(async (files: File[]) => {
     if (files.length === 0) return
 
     setCsvData(null)
     onClearError()
 
     const file = files[0]
     const parsedData = await onParseFile(file)
 
     if (parsedData) {
       setCsvData(parsedData)
     }
-  }
+  }, [onParseFile, onClearError])
 
-  const handleFilesRejected = (
+  const handleFilesRejected = useCallback((
     fileRejections: DropzoneState['fileRejections']
   ) => {
     // ... body unchanged ...
-  }
+  }, [pushToast, labels.toastRejectionTitle, labels.toastRejectionDefaultMessage, labels.toastFileTooLargeMessage, labels.toastFileInvalidTypeMessage, labels.toastTooManyFilesMessage])

As per coding guidelines, packages/**/src/components/**: "Check rendering performance … use memo/useMemo/useCallback when appropriate."

Also applies to: 176-204

packages/components/src/molecules/Dropzone/Dropzone.tsx (1)

206-228: Using array index as key for file lists — acceptable here but worth noting.

Since acceptedFiles from react-dropzone can accumulate across multiple drops without resetting, the rendered list may grow unexpectedly. If the Dropzone is intended as a single-use upload trigger (e.g., for the CSV flow), consider whether the parent should control displayed files instead.

For the keys themselves, file.name isn't guaranteed unique either, so index is fine for now.

Copy link

@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: 1

🤖 Fix all issues with AI agents
In `@packages/core/src/components/search/SearchInput/SearchInput.tsx`:
- Around line 336-349: The FileUploadCard prop spread currently places
...(fileUploadCardProps ?? DEFAULT_FILE_UPLOAD_CARD_PROPS) after explicit props
so CMS/default props can override handlers; change the JSX so the spread appears
before the explicit props (e.g., move the ...(fileUploadCardProps ??
DEFAULT_FILE_UPLOAD_CARD_PROPS) above onDismiss, onFileSelect,
onDownloadTemplate, onSearch, isOpen, isUploading, hasError) ensuring the
explicit handlers in this component (handleDismiss, handleFileSelect,
handleDownloadTemplate, handleSearch, formatFileSize, formatFileName,
isUploadOpen/hasFile/fileUploadVisible logic, isCsvProcessing, csvError) always
take precedence.
🧹 Nitpick comments (2)
packages/components/src/hooks/useCSVParser.ts (1)

137-175: Zero quantity is accepted — intentional for bulk orders?

Line 153 rejects negative quantities but allows quantity = 0. For a bulk-order/quick-order flow, a zero-quantity row is likely a no-op. Consider rejecting numericQuantity <= 0 unless there's a use case for adding zero-quantity items.

-        if (Number.isNaN(numericQuantity) || numericQuantity < 0) {
+        if (Number.isNaN(numericQuantity) || numericQuantity <= 0) {
packages/components/src/index.ts (1)

2-9: Redundant re-exports: export * already covers these.

Line 2's export * from './hooks' re-exports everything from hooks/index.ts, which already includes useCSVParser, DEFAULT_FILE_UPLOAD_OPTIONS, useFileUpload, and FileUploadOptions. The explicit re-exports on lines 3–9 are duplicates. They won't break anything, but the type-only re-exports (CSVData, CSVParserError, CSVParserOptions) are the only ones actually needed here since they aren't exported from hooks/index.ts.

Lines 8–9 (DEFAULT_FILE_UPLOAD_OPTIONS and FileUploadOptions) can be removed since hooks/index.ts already exports them.

Suggested cleanup
 export * from './hooks'
 export type {
   CSVData,
   CSVParserError,
   CSVParserOptions,
 } from './hooks/useCSVParser'
-export { DEFAULT_FILE_UPLOAD_OPTIONS } from './hooks/useFileUpload'
-export type { FileUploadOptions } from './hooks/useFileUpload'

lariciamota added a commit that referenced this pull request Feb 19, 2026
## Summary

- Replace hardcoded version references (`^3.97.0-dev.2`) with
`workspace:*` protocol for `@faststore/components` and `@faststore/ui`
in `packages/ui/package.json` and `packages/storybook/package.json`
- This ensures local packages are always resolved from the monorepo
workspace during development

## Motivation

Extracted from PR #3117 as requested in [code
review](#3117) by @lariciamota, so
we can identify possible problems easier by isolating this change.

## Changed files

- `packages/ui/package.json` — `@faststore/components` dependency and
peerDependency changed to `workspace:*`
- `packages/storybook/package.json` — `@faststore/components` and
`@faststore/ui` dependencies changed to `workspace:*`

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Switched internal package references in Storybook and UI to
workspace-based declarations to improve build consistency.
* Aligned dependency declarations across the monorepo to reduce version
mismatch risks and streamline local development and local testing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Larícia Mota <laricia.mota@vtex.com.br>
Copy link
Contributor

@lariciamota lariciamota left a comment

Choose a reason for hiding this comment

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

Deixando aprovado já, mas dá um rebase antes da gente mergear pfv, já que o #3209 foi mergeado na dev :D

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.

7 participants