Skip to content

Training Demo #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open

Training Demo #1

wants to merge 39 commits into from

Conversation

jaruesink
Copy link

@jaruesink jaruesink commented Mar 14, 2025

Summary by CodeRabbit

  • New Features

    • Rebranded the site under the "360 Training" identity, focusing on professional development and training courses.
    • Enhanced product pages with detailed course information (duration, level, rating) and clear "Enroll" calls-to-action.
    • Introduced a new design prompt for an e-commerce training courses website.
    • Added a new GitHub Actions workflow for deployment processes.
    • Implemented tools for managing product review responses, including creation, updating, and deletion functionalities.
    • Added new workflows for managing stock locations, including creation, updating, and deletion.
  • Style & UI Improvements

    • Revamped header, footer, and hero sections with modern typography, refreshed color schemes, and intuitive layouts.
    • Updated navigation with new categories such as Business Courses, Technical Skills, and Leadership Training.
    • Improved image display in the product gallery for better visual appeal.
    • Added rounded corners to various UI elements for a modern look.
  • Other Updates

    • Streamlined behind-the-scenes processes for improved performance and stability.
    • Updated metadata and favicon for enhanced branding consistency.
    • Updated dependencies to their latest versions for better stability and performance.
    • Enhanced testing strategies for product and category management tools.

Copy link

coderabbitai bot commented Mar 14, 2025

Walkthrough

This pull request introduces a new GitHub Actions workflow for deploying a training demo, updates several Medusa dependency versions and seed scripts, and rebrands the storefront from a coffee-centric theme to a training platform. It also enhances product option handling, adjusts navigation and UI components, updates site documentation and configuration, and streamlines server-side cart creation and checkout logic.

Changes

File(s) Change Summary
.github/workflows/training-demo-deploy.yaml Added new GitHub Actions workflow triggered on pushes to training-demo and main branches with jobs: setup, deploy-storefront, and deploy-medusa using environment variables.
apps/medusa/package.json
apps/medusa/src/scripts/seed.ts
apps/medusa/src/scripts/seed/products.ts
apps/medusa/src/scripts/seed/reviews.ts
Updated Medusa dependency versions from 2.5.0 to 2.6.0; refactored seeding scripts for demo data, including dynamic training product creation and revised review generation (shifting from coffee to course context).
apps/storefront/ai/DesignPrompt.md
apps/storefront/ai/Gameplan.md
apps/storefront/ai/Project.md
Introduced new design prompt, detailed project gameplan, and project document outlining rebranding to the “360 Training Demo”.
apps/storefront/app/components/LogoStoreName/LogoStoreName.tsx
apps/storefront/app/components/layout/footer/Footer.tsx
apps/storefront/app/components/layout/header/Header.tsx
apps/storefront/app/components/product/ProductListHeader.tsx
apps/storefront/app/components/product/ProductListItem.tsx
apps/storefront/app/components/reviews/ProductReviewList.tsx
apps/storefront/app/components/sections/Hero.tsx
apps/storefront/app/components/sections/ListItems.tsx
apps/storefront/app/routes/_index.tsx
apps/storefront/app/routes/categories.$categoryHandle.tsx
apps/storefront/app/root.tsx
apps/storefront/app/routes/about-us.tsx (deleted)
Rebranded storefront components from a coffee business theme to 360 Training, updating text labels, static store name, fonts, colors, layout, overlay effects, and deleting the outdated About Us page.
apps/storefront/app/components/product/ProductOptionSelectorRadio.tsx
apps/storefront/app/components/product/ProductOptionSelectorSelect.tsx
apps/storefront/app/templates/ProductTemplate.tsx
Enhanced product option handling by adding pricing fields (min, max, exact prices, discount percentage), improved default selection logic and validators, and removed the product reviews component.
apps/storefront/app/routes/checkout._index.tsx Streamlined shipping method selection and payment session logic for checkout functionality.
apps/storefront/app/styles/global.css
apps/storefront/tailwind.config.js
apps/storefront/libs/config/site/navigation-items.ts
apps/storefront/libs/config/site/site-settings.ts
apps/storefront/libs/util/products.ts
apps/storefront/libs/util/server/data/cart.server.ts
apps/storefront/libs/util/server/root.server.ts
Updated site styling with a new blue/teal color palette and 'Inter' & 'Montserrat' fonts; modified navigation items and favicon URL; enhanced product utility functions; and added a new createCart function with revised cart handling logic.

Sequence Diagram(s)

sequenceDiagram
    participant Push as Git Push
    participant Setup as Setup Job
    participant StoreDeploy as Deploy-Storefront Job
    participant MedusaDeploy as Deploy-Medusa Job

    Push->>Setup: Trigger on push to training-demo/main branch
    Setup->>Setup: Checkout code, setup Node.js, install dependencies, cache, yarn build
    Setup->>StoreDeploy: On successful completion, trigger storefront deployment
    Setup->>MedusaDeploy: On successful completion, trigger Medusa deployment
    StoreDeploy->>StoreDeploy: Checkout, install (lookup-only), push Docker image, perform Helm upgrade for storefront
    MedusaDeploy->>MedusaDeploy: Checkout, install (lookup-only), push Docker image, perform Helm upgrade for Medusa
Loading
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 2

🧹 Nitpick comments (16)
apps/storefront/libs/util/server/root.server.ts (1)

62-64: Rebranding Update Confirmed.

The store’s name has been updated to "360 Training" which aligns with the overall rebranding effort as described in the PR objectives and AI summary. Please ensure that any external integrations or components that previously referenced the old value ("BARRIO") are updated accordingly for full consistency across the app.

apps/storefront/libs/config/site/site-settings.ts (1)

7-7: Favicon update for rebranding

The favicon has been updated to point to the 360training.com domain, aligning with the overall rebranding direction.

However, consider adding this favicon to your local assets instead of linking to an external domain to:

  1. Prevent loading issues if the external URL changes
  2. Improve page load performance
  3. Reduce dependency on external resources
apps/storefront/app/components/LogoStoreName/LogoStoreName.tsx (1)

18-20: Consider keeping dynamic store name capability

While hardcoding the store name works for immediate rebranding, it removes the flexibility of changing the name through the store settings in the future. Consider using a fallback approach instead.

- // Override the store name for the 360 Training platform
- const displayName = '360 TRAINING';
+ // Use '360 TRAINING' as default, but allow overrides from store settings
+ const displayName = store.name === 'Barrio Coffee' ? '360 TRAINING' : store.name;
apps/storefront/ai/DesignPrompt.md (1)

1-12: Clear and comprehensive design guidelines

The design prompt effectively outlines the requirements for an e-commerce training courses website with a focus on modern, clean, and minimalist design principles. The key elements are well-defined and address both visual aspects and user experience considerations.

Consider adding:

  • Specific color palette guidelines or references
  • Typography recommendations or standards
  • Explicit accessibility standards (e.g., WCAG compliance level)

These additions would further enhance implementation consistency across the platform.

apps/storefront/ai/Project.md (1)

1-27: Comprehensive project scope for 360 Training Demo

The project document provides clear guidance for rebranding from Barrio Coffee to 360 Training, with well-defined scope, structure, and design guidelines. The specific course examples for each category help clarify the intended content.

There are formatting issues to address:

  • Hard tabs are used throughout the document, which should be replaced with spaces for consistent formatting
  • Line 14 contains a typographical issue with the colon before "such as"

Consider this correction:

-	•	Seed each category with example courses such as:
+	•	Seed each category with example courses such as:

Additionally, you might want to standardize the list formatting throughout the document for consistency.

🧰 Tools
🪛 LanguageTool

[typographical] ~14-~14: Do not use a colon (:) before a series that is introduced by a preposition (‘as’). Remove the colon or add a noun or a noun phrase after the preposition.
Context: ...each category with example courses such as: • Business: “Introduction to 360 Train...

(RP_COLON)


[grammar] ~26-~26: Incorrect singular possessive form after the number (360). Did you mean “Trainings” or “Trainings'”?
Context: ...essional aesthetic that aligns with 360 Training’s brand identity. • The goal is to provi...

(INCORRECT_POSSESSIVE_FORM_AFTER_A_NUMBER)

🪛 markdownlint-cli2 (0.17.2)

7-7: Hard tabs
Column: 1

(MD010, no-hard-tabs)


7-7: Hard tabs
Column: 4

(MD010, no-hard-tabs)


8-8: Hard tabs
Column: 1

(MD010, no-hard-tabs)


8-8: Hard tabs
Column: 3

(MD010, no-hard-tabs)


9-9: Hard tabs
Column: 1

(MD010, no-hard-tabs)


9-9: Hard tabs
Column: 3

(MD010, no-hard-tabs)


10-10: Hard tabs
Column: 1

(MD010, no-hard-tabs)


10-10: Hard tabs
Column: 3

(MD010, no-hard-tabs)


11-11: Hard tabs
Column: 1

(MD010, no-hard-tabs)


11-11: Hard tabs
Column: 4

(MD010, no-hard-tabs)


12-12: Hard tabs
Column: 1

(MD010, no-hard-tabs)


12-12: Hard tabs
Column: 3

(MD010, no-hard-tabs)


13-13: Hard tabs
Column: 1

(MD010, no-hard-tabs)


13-13: Hard tabs
Column: 3

(MD010, no-hard-tabs)


14-14: Hard tabs
Column: 1

(MD010, no-hard-tabs)


14-14: Hard tabs
Column: 3

(MD010, no-hard-tabs)


15-15: Hard tabs
Column: 1

(MD010, no-hard-tabs)


15-15: Hard tabs
Column: 3

(MD010, no-hard-tabs)


16-16: Hard tabs
Column: 1

(MD010, no-hard-tabs)


16-16: Hard tabs
Column: 3

(MD010, no-hard-tabs)


17-17: Hard tabs
Column: 1

(MD010, no-hard-tabs)


17-17: Hard tabs
Column: 3

(MD010, no-hard-tabs)


20-20: Hard tabs
Column: 1

(MD010, no-hard-tabs)


20-20: Hard tabs
Column: 3

(MD010, no-hard-tabs)


21-21: Hard tabs
Column: 1

(MD010, no-hard-tabs)


21-21: Hard tabs
Column: 3

(MD010, no-hard-tabs)


22-22: Hard tabs
Column: 1

(MD010, no-hard-tabs)


22-22: Hard tabs
Column: 3

(MD010, no-hard-tabs)


25-25: Hard tabs
Column: 1

(MD010, no-hard-tabs)


25-25: Hard tabs
Column: 3

(MD010, no-hard-tabs)


26-26: Hard tabs
Column: 1

(MD010, no-hard-tabs)


26-26: Hard tabs
Column: 3

(MD010, no-hard-tabs)


27-27: Hard tabs
Column: 1

(MD010, no-hard-tabs)


27-27: Hard tabs
Column: 3

(MD010, no-hard-tabs)

apps/storefront/app/routes/checkout._index.tsx (1)

52-71: Enhanced payment session handling

The refactored ensureCartPaymentSessions function now has a more efficient flow, fetching payment providers only when necessary. This approach reduces unnecessary API calls and provides a clearer logic path.

One additional improvement to consider:

Consider adding more explicit error handling for edge cases, such as when payment providers exist but creating a payment session fails:

  if (!activeSession) {
    const paymentProviders = await listCartPaymentProviders(cart.region_id!);
    if (!paymentProviders.length) return activeSession;

    const provider = paymentProviders.find((p) => p.id !== SYSTEM_PROVIDER_ID) || paymentProviders[0];

+   try {
      const { payment_collection } = await initiatePaymentSession(request, cart, {
        provider_id: provider.id,
      });

      activeSession = payment_collection.payment_sessions?.find((session) => session.status === 'pending');
+   } catch (error) {
+     console.error('Failed to initiate payment session:', error);
+     // Consider appropriate fallback behavior here
+   }
  }
apps/storefront/ai/Gameplan.md (1)

1-89: Well-structured project plan with clear objectives

This Gameplan document provides an excellent roadmap for the project, with well-defined phases, task tracking, and success metrics. The phased approach (setup, home page, category pages, polish) makes the project manageable and trackable.

There are a few minor grammatical issues that could be addressed:

  • Line 4: Consider "training course platform" instead of "training courses platform"
  • Line 59: Add "the" before "360 Training platform"
🧰 Tools
🪛 LanguageTool

[uncategorized] ~4-~4: The grammatical number of this noun doesn’t look right. Consider replacing it.
Context: ...offee demo into a professional training courses platform for 360 Training, focusing on ...

(AI_EN_LECTOR_REPLACEMENT_NOUN_NUMBER)


[uncategorized] ~12-~12: You might be missing the article “the” here.
Context: ...: Quick Setup & Branding ✅ - [x] Set up basic Tailwind theme - [x] Update favicon and...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~33-~33: The grammatical number of this noun doesn’t look right. Consider replacing it.
Context: ...tegory description - [x] Update product cards styling - [x] Modern card design - ...

(AI_EN_LECTOR_REPLACEMENT_NOUN_NUMBER)


[grammar] ~59-~59: Possible agreement error. The noun ‘platform’ seems to be countable.
Context: ...eta description to reflect 360 Training platform - Implemented basic branding changes - ...

(CD_NN)

apps/storefront/app/components/product/ProductOptionSelectorRadio.tsx (1)

102-112: Consider extracting price display to a reusable component

The price display logic is duplicated between ProductOptionSelectorSelect and ProductOptionSelectorRadio. Consider extracting this to a reusable component to maintain consistency and simplify future updates.

+ // New file: apps/storefront/app/components/product/ProductOptionPrice.tsx
+ import { FC } from 'react';
+ import { formatPrice } from '@libs/util/prices';
+ 
+ interface ProductOptionPriceProps {
+   minPrice?: number;
+   maxPrice?: number;
+   exactPrice?: number;
+   discountPercentage?: number;
+   currencyCode: string;
+ }
+ 
+ export const ProductOptionPrice: FC<ProductOptionPriceProps> = ({
+   minPrice,
+   maxPrice,
+   exactPrice,
+   discountPercentage,
+   currencyCode,
+ }) => {
+   // Format the price display
+   let priceDisplay = '';
+   let discountDisplay = '';
+ 
+   if (minPrice !== undefined && maxPrice !== undefined) {
+     if (minPrice === maxPrice) {
+       // Single price
+       priceDisplay = formatPrice(minPrice, { currency: currencyCode });
+     } else {
+       // Price range
+       priceDisplay = `${formatPrice(minPrice, { currency: currencyCode })} – ${formatPrice(maxPrice, { currency: currencyCode })}`;
+     }
+   } else if (exactPrice !== undefined) {
+     // Exact price
+     priceDisplay = formatPrice(exactPrice, { currency: currencyCode });
+ 
+     // Format discount if available
+     if (discountPercentage) {
+       discountDisplay = `${discountPercentage}% off`;
+     }
+   }
+ 
+   if (!priceDisplay) return null;
+ 
+   return (
+     <div className="text-right">
+       <span className="text-sm font-normal text-gray-500">{priceDisplay}</span>
+       {discountDisplay && (
+         <span className="ml-1 text-xs font-medium text-green-600">({discountDisplay})</span>
+       )}
+     </div>
+   );
+ };

Then use this component in both selectors:

// In ProductOptionSelectorRadio.tsx
+ import { ProductOptionPrice } from './ProductOptionPrice';

// Replace lines 102-110 with:
+ <ProductOptionPrice
+   minPrice={optionValue.minPrice}
+   maxPrice={optionValue.maxPrice}
+   exactPrice={optionValue.exactPrice}
+   discountPercentage={optionValue.discountPercentage}
+   currencyCode={currencyCode}
+ />
apps/storefront/app/routes/_index.tsx (1)

212-215: Image reuse may reduce visual variety.

The same hero image is used at both the top (line 43) and bottom (line 213) of the page. Consider using a different image in one of these locations to add more visual variety to the page.

- url: 'https://images.unsplash.com/photo-1522202176988-66273c2fd55f?w=800&auto=format&fit=crop&q=60',
- alt: '360 Training background',
+ url: 'https://images.unsplash.com/photo-1517245386807-bb43f82c33c4?w=800&auto=format&fit=crop&q=60',
+ alt: 'Professional growth through training',
apps/medusa/src/scripts/seed.ts (1)

504-534: Automatic environment setup for local development.

The new functionality to update the storefront's .env file with the publishable API key is a great developer experience improvement. It reduces manual configuration steps for local development.

However, there's a potential issue with the path resolution that assumes a specific directory structure.

Consider making the path resolution more robust by checking for alternative locations or making the path configurable:

- const envFilePath = path.resolve(__dirname, '../../../../apps/storefront/.env');
+ // Try multiple possible locations
+ const possiblePaths = [
+   path.resolve(__dirname, '../../../../apps/storefront/.env'),
+   path.resolve(__dirname, '../../../storefront/.env'),
+   process.env.STOREFRONT_ENV_PATH, // Allow configuration via env var
+ ];
+ 
+ let envFilePath = null;
+ for (const p of possiblePaths) {
+   if (p && fs.existsSync(p)) {
+     envFilePath = p;
+     break;
+   }
+ }
+ 
+ if (!envFilePath) {
+   logger.warn('Could not find storefront .env file in any of the expected locations');
+   return;
+ }
apps/medusa/src/scripts/seed/products.ts (2)

32-107: Flexible and dynamic product data generation.

The buildBaseTrainingProductData function is well-designed to handle combinations of durations and levels, creating appropriate variants. The function is robust and reusable.

One suggestion for improvement is to add validation for the price keys:

for (const duration of durations) {
  for (const level of levels) {
    const variantKey = `${duration} / ${level}`;
    const priceInfo = prices[variantKey];

    if (priceInfo) {
      variants.push({
        title: variantKey,
        prices: [
          {
            currency_code: 'usd',
            amount: priceInfo.usd,
          },
          {
            currency_code: 'cad',
            amount: priceInfo.cad,
          },
        ],
        options: {
          Duration: duration,
          Level: level,
        },
        manage_inventory: false,
        allow_backorder: true,
      });
+   } else {
+     console.warn(`Warning: No price found for variant ${variantKey}. Skipping this variant.`);
    }
  }
}

127-184: Comprehensive training product data.

The product data is well-structured and includes all necessary information for a training platform, including appropriate titles, descriptions, durations, levels, and prices.

One potential issue is that the filter for tag IDs doesn't check for undefined correctly:

- tag_ids: [beginnerTag, featuredTag, newTag].filter(Boolean) as string[],
+ tag_ids: [beginnerTag, featuredTag, newTag].filter((tag): tag is string => !!tag),

This ensures type safety when filtering out undefined values, as TypeScript's type narrowing will recognize that only string values remain after filtering.

apps/medusa/src/scripts/seed/reviews.ts (1)

105-114: Randomized replies no longer use the input parameter’s rating or name.
If personalization is still desired, consider refactoring to incorporate the review attributes again or rename the parameter to avoid confusion.

apps/storefront/app/templates/ProductTemplate.tsx (2)

226-263: handleOptionChangeBySelect correctly resets subsequent options.
The logic is clear, though handleOptionChangeByRadio is very similar. Consider consolidating to avoid code duplication.


266-299: handleOptionChangeByRadio mirrors the select-based approach.
Refactoring both handlers into a unified solution could reduce duplication.

apps/storefront/libs/config/site/navigation-items.ts (1)

5-27: Consider using sequential IDs for better maintainability

I notice the IDs for navigation items aren't sequential (6, 5, 7). While this doesn't affect functionality since IDs only need to be unique, sequential IDs would improve maintainability. Consider reorganizing these for better consistency if there's no specific reason for the current numbering.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 018858e and 6ebcbff.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (31)
  • .github/workflows/training-demo-deploy.yaml (1 hunks)
  • apps/medusa/package.json (2 hunks)
  • apps/medusa/src/scripts/seed.ts (9 hunks)
  • apps/medusa/src/scripts/seed/products.ts (1 hunks)
  • apps/medusa/src/scripts/seed/reviews.ts (1 hunks)
  • apps/storefront/ai/DesignPrompt.md (1 hunks)
  • apps/storefront/ai/Gameplan.md (1 hunks)
  • apps/storefront/ai/Project.md (1 hunks)
  • apps/storefront/app/components/LogoStoreName/LogoStoreName.tsx (1 hunks)
  • apps/storefront/app/components/layout/footer/Footer.tsx (2 hunks)
  • apps/storefront/app/components/layout/header/Header.tsx (5 hunks)
  • apps/storefront/app/components/product/ProductListHeader.tsx (1 hunks)
  • apps/storefront/app/components/product/ProductListItem.tsx (1 hunks)
  • apps/storefront/app/components/product/ProductOptionSelectorRadio.tsx (1 hunks)
  • apps/storefront/app/components/product/ProductOptionSelectorSelect.tsx (1 hunks)
  • apps/storefront/app/components/reviews/ProductReviewList.tsx (1 hunks)
  • apps/storefront/app/components/sections/Hero.tsx (2 hunks)
  • apps/storefront/app/components/sections/ListItems.tsx (2 hunks)
  • apps/storefront/app/root.tsx (2 hunks)
  • apps/storefront/app/routes/_index.tsx (3 hunks)
  • apps/storefront/app/routes/about-us.tsx (0 hunks)
  • apps/storefront/app/routes/categories.$categoryHandle.tsx (1 hunks)
  • apps/storefront/app/routes/checkout._index.tsx (2 hunks)
  • apps/storefront/app/styles/global.css (5 hunks)
  • apps/storefront/app/templates/ProductTemplate.tsx (10 hunks)
  • apps/storefront/libs/config/site/navigation-items.ts (2 hunks)
  • apps/storefront/libs/config/site/site-settings.ts (1 hunks)
  • apps/storefront/libs/util/products.ts (2 hunks)
  • apps/storefront/libs/util/server/data/cart.server.ts (2 hunks)
  • apps/storefront/libs/util/server/root.server.ts (1 hunks)
  • apps/storefront/tailwind.config.js (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/storefront/app/routes/about-us.tsx
🧰 Additional context used
🪛 LanguageTool
apps/storefront/ai/Project.md

[typographical] ~14-~14: Do not use a colon (:) before a series that is introduced by a preposition (‘as’). Remove the colon or add a noun or a noun phrase after the preposition.
Context: ...each category with example courses such as: • Business: “Introduction to 360 Train...

(RP_COLON)


[grammar] ~26-~26: Incorrect singular possessive form after the number (360). Did you mean “Trainings” or “Trainings'”?
Context: ...essional aesthetic that aligns with 360 Training’s brand identity. • The goal is to provi...

(INCORRECT_POSSESSIVE_FORM_AFTER_A_NUMBER)

apps/storefront/ai/Gameplan.md

[uncategorized] ~4-~4: The grammatical number of this noun doesn’t look right. Consider replacing it.
Context: ...offee demo into a professional training courses platform for 360 Training, focusing on ...

(AI_EN_LECTOR_REPLACEMENT_NOUN_NUMBER)


[uncategorized] ~12-~12: You might be missing the article “the” here.
Context: ...: Quick Setup & Branding ✅ - [x] Set up basic Tailwind theme - [x] Update favicon and...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~33-~33: The grammatical number of this noun doesn’t look right. Consider replacing it.
Context: ...tegory description - [x] Update product cards styling - [x] Modern card design - ...

(AI_EN_LECTOR_REPLACEMENT_NOUN_NUMBER)


[grammar] ~59-~59: Possible agreement error. The noun ‘platform’ seems to be countable.
Context: ...eta description to reflect 360 Training platform - Implemented basic branding changes - ...

(CD_NN)

🪛 markdownlint-cli2 (0.17.2)
apps/storefront/ai/Project.md

7-7: Hard tabs
Column: 1

(MD010, no-hard-tabs)


7-7: Hard tabs
Column: 4

(MD010, no-hard-tabs)


8-8: Hard tabs
Column: 1

(MD010, no-hard-tabs)


8-8: Hard tabs
Column: 3

(MD010, no-hard-tabs)


9-9: Hard tabs
Column: 1

(MD010, no-hard-tabs)


9-9: Hard tabs
Column: 3

(MD010, no-hard-tabs)


10-10: Hard tabs
Column: 1

(MD010, no-hard-tabs)


10-10: Hard tabs
Column: 3

(MD010, no-hard-tabs)


11-11: Hard tabs
Column: 1

(MD010, no-hard-tabs)


11-11: Hard tabs
Column: 4

(MD010, no-hard-tabs)


12-12: Hard tabs
Column: 1

(MD010, no-hard-tabs)


12-12: Hard tabs
Column: 3

(MD010, no-hard-tabs)


13-13: Hard tabs
Column: 1

(MD010, no-hard-tabs)


13-13: Hard tabs
Column: 3

(MD010, no-hard-tabs)


14-14: Hard tabs
Column: 1

(MD010, no-hard-tabs)


14-14: Hard tabs
Column: 3

(MD010, no-hard-tabs)


15-15: Hard tabs
Column: 1

(MD010, no-hard-tabs)


15-15: Hard tabs
Column: 3

(MD010, no-hard-tabs)


16-16: Hard tabs
Column: 1

(MD010, no-hard-tabs)


16-16: Hard tabs
Column: 3

(MD010, no-hard-tabs)


17-17: Hard tabs
Column: 1

(MD010, no-hard-tabs)


17-17: Hard tabs
Column: 3

(MD010, no-hard-tabs)


20-20: Hard tabs
Column: 1

(MD010, no-hard-tabs)


20-20: Hard tabs
Column: 3

(MD010, no-hard-tabs)


21-21: Hard tabs
Column: 1

(MD010, no-hard-tabs)


21-21: Hard tabs
Column: 3

(MD010, no-hard-tabs)


22-22: Hard tabs
Column: 1

(MD010, no-hard-tabs)


22-22: Hard tabs
Column: 3

(MD010, no-hard-tabs)


25-25: Hard tabs
Column: 1

(MD010, no-hard-tabs)


25-25: Hard tabs
Column: 3

(MD010, no-hard-tabs)


26-26: Hard tabs
Column: 1

(MD010, no-hard-tabs)


26-26: Hard tabs
Column: 3

(MD010, no-hard-tabs)


27-27: Hard tabs
Column: 1

(MD010, no-hard-tabs)


27-27: Hard tabs
Column: 3

(MD010, no-hard-tabs)

🔇 Additional comments (105)
apps/storefront/app/routes/categories.$categoryHandle.tsx (2)

46-46: Font standardization looks good.

Changing from font-ballet to font-inter for the PageHeading aligns with the application's rebranding from a coffee theme to a training platform, providing a more professional look.


53-53: Consistent font styling applied.

Good job standardizing the navigation text to use font-inter instead of font-italiana, maintaining visual consistency with the heading styles and overall application rebrand.

apps/storefront/libs/util/server/data/cart.server.ts (2)

25-27: Good addition of createCart function

This new function provides a clean way to create carts with authentication headers, making it reusable across the codebase.


74-93: Improved cart creation flow

The refactored addToCart function streamlines the cart creation process by:

  1. Checking for an existing cart first
  2. Creating a new cart with the items directly if none exists
  3. Eliminating unnecessary code paths

This approach is more efficient and easier to follow than the previous implementation.

apps/storefront/app/components/product/ProductListHeader.tsx (1)

23-23: Font style update aligns with rebranding

The font change from 'font-italiana' to 'font-inter font-normal tracking-wider' is consistent with the rebranding effort from a coffee-centric theme to a training platform.

apps/storefront/app/components/reviews/ProductReviewList.tsx (1)

63-63: Business name updated for rebranding

The change from "Barrio's Response" to "360 Training's Response" properly reflects the rebranding from a coffee shop to a training platform.

apps/storefront/tailwind.config.js (1)

21-22: Font family additions look good

The addition of 'Inter' and 'Montserrat' font families aligns with the rebranding effort from a coffee-centric theme to a training platform. These are appropriate choices for a professional development site.

apps/storefront/app/components/layout/footer/Footer.tsx (3)

44-44: Color scheme change enhances brand identity

The change from bg-accent-50 to bg-primary-800 with white text creates a more professional and distinct appearance, appropriate for a training platform.


49-54: Content update aligns with rebranding

The heading change to "Education & Excellence" and the updated paragraph text properly reflect the new focus on professional development and training courses.


83-88: Location section updated appropriately

The rename from "Location" to "Training Center" and updated hours reflect the business model change from a coffee shop to a professional training center.

apps/storefront/app/components/LogoStoreName/LogoStoreName.tsx (1)

28-30: Font updated to match new branding

The font change from font-aboreto to font-inter properly aligns with the overall design system changes for the rebranding.

apps/storefront/app/components/sections/ListItems.tsx (3)

21-21: Height improvement for grid items

The addition of h-full ensures consistent heights for items in the same row, improving the visual layout. The font change to Montserrat aligns with the new branding.


39-39: Font updated consistently

The change from font-italiana to font-inter for the section title maintains consistency with the new brand identity.


44-44: Layout improvement with grid

Switching from a flexbox to a grid layout is an excellent improvement for displaying items in a structured manner across different screen sizes. The 3-column layout on medium screens will provide a better visual organization.

apps/medusa/package.json (1)

36-41:

❓ Verification inconclusive

Package versions have been updated to 2.6.0.

All Medusa-related dependencies have been updated from 2.5.0 to 2.6.0, which is good for keeping dependencies in sync.

Also applies to: 50-50


🏁 Script executed:

#!/bin/bash
# Check for breaking changes or migration notes between Medusa 2.5.0 and 2.6.0

echo "Checking Medusa release notes for version 2.6.0..."
curl -s "https://api.github.com/repos/medusajs/medusa/releases" | jq '.[] | select(.tag_name | test("v2.6.0")) | .body' | grep -i "breaking\|migration"

echo "Checking if the project includes migration files for this update..."
find . -path "*/migrations/*" -type f -name "*.ts" | sort

Length of output: 18962


Update: Medusa Dependencies Upgrade to 2.6.0 & Breaking Change Notice

The Medusa-related dependencies in apps/medusa/package.json have been successfully updated from 2.5.0 to 2.6.0. However, a review of the Medusa 2.6.0 release notes reveals a breaking change in the authentication flow:

  • Breaking Change in Password Reset Flow:
    The release notes indicate that the token used for updating provider identity (e.g., in the reset password endpoint) is now required to be supplied as a Bearer token in the authorization header rather than as a query parameter. If your client (e.g., Next.js storefront) or server code interacts with this endpoint, please ensure it is updated accordingly.

  • Migration Files:
    The verification script did not find any migration files related to this update, which is expected given that this change is limited to request handling rather than database schema migrations.

Please verify that any parts of your application using the affected authentication endpoint have been updated to pass the token via the authorization header.

apps/storefront/app/root.tsx (2)

22-24: Site metadata has been updated to reflect the new training platform focus.

The title and description have been changed from coffee-related content to training and professional development, which aligns with the overall rebranding of the application.


77-86:

❓ Verification inconclusive

New fonts have been added to support the rebranding.

Added Inter and Montserrat fonts, and repositioned the Italiana font. This provides more typography options for the refreshed UI design.


🏁 Script executed:

#!/bin/bash
# Check if these new fonts are being used in CSS files

echo "Checking for font usage in CSS files..."
rg -A 3 "font-inter|font-montserrat" --type css

Length of output: 138


Action Required: Manually verify the new fonts’ usage in the styling.

The updated file at apps/storefront/app/root.tsx now loads additional Google fonts to support the rebranding—specifically, Inter, Montserrat, Ballet, Italiana, Sen, and Aboreto. The changes highlight that Inter and Montserrat have been added and the Italiana font has been repositioned to broaden the typography options for the refreshed UI design.

Although the change itself is straightforward, an automated search in CSS files for references such as “font-inter” or “font-montserrat” returned no results. Please ensure that these fonts are actually applied in the CSS (or through inline styles/other styling mechanisms) across the codebase. If these fonts are used dynamically or via different naming conventions, a manual verification of the UI styling is recommended.

  • Check CSS usage: Verify if the new fonts (especially Inter and Montserrat) are referenced directly in any CSS or style files.
  • Confirm UI impact: Ensure that the repositioned Italiana font and the new typography options are reflected in the UI as intended.
apps/storefront/app/components/product/ProductListItem.tsx (4)

17-20: Product metadata extraction for course-specific information.

Good addition of course-specific metadata with sensible defaults. This enhances the product display with educational context.


23-38: Enhanced product card styling with course level indicator.

The product card has been redesigned with improved styling and a prominent course level indicator, making it more suitable for a training platform.


40-54: Added course metadata display with duration and rating.

The course title and metadata section provides clear information about the course duration and rating, improving the user experience for course selection.


56-64: Added pricing with call-to-action button.

The pricing section has been enhanced with a clear "Enroll" button, providing a strong call-to-action for users interested in the course.

apps/storefront/app/components/layout/header/Header.tsx (4)

28-28: Updated header background styling.

Changed the header background from a specific hex value to a predefined color class, which improves maintainability and consistency with the design system.


43-43: Updated hover and focus states for icons.

The hover and focus styles for icons have been updated to use the primary-600 color, maintaining consistency with the new color scheme.

Also applies to: 104-104, 123-123


46-52: Updated cart indicator styling.

Changed the background color for the cart item count badge from primary-500 to accent-500, which provides better visual distinction and aligns with the new design system.

Also applies to: 107-113


78-84: Improved navigation link styling.

Updated the navigation links to use medium font weight and accent colors for hover states, which improves readability and user interaction feedback.

.github/workflows/training-demo-deploy.yaml (1)

1-125: Well-structured workflow for training demo deployment

This GitHub Actions workflow effectively handles the deployment process for both the storefront and Medusa applications. The workflow is triggered on pushes to the training-demo and main branches, and it's organized into three logical jobs: setup, deploy-storefront, and deploy-medusa.

Some considerations for potential improvements:

  • Consider adding explicit health checks or verification steps after deployment to ensure everything is working as expected
  • You might want to implement retry mechanisms for deployment failures
  • Adding comments for complex steps would improve maintainability

Overall, the workflow follows best practices with proper dependency management between jobs and secure handling of sensitive information through environment variables.

apps/storefront/app/components/sections/Hero.tsx (4)

15-17: Well-designed component enhancement with customizable overlay

The addition of overlayOpacity and overlayColor props improves the component's configurability, allowing for visual customization without modifying the component code.


25-27: Good default values for new props

Setting sensible defaults (opacity: 0.7, color: rgba(0, 0, 0, 0.75)) ensures the component works well out of the box while still allowing customization when needed.


41-47: Clean implementation of customizable overlay

The overlay implementation uses proper positioning and z-index for layering, with a nicely implemented gradient background that enhances visual appeal.


48-48: Proper z-index management

Adding the relative class ensures proper stacking context, keeping the content above the overlay while maintaining semantic structure.

apps/storefront/ai/Gameplan.md (2)

38-43: Incomplete "Essential Polish" phase

You have one remaining task in the Essential Polish phase (Simple loading states) that's marked as in progress. Ensure this is completed before finalizing the demo.

How complex are the loading states implementation? Would you like me to suggest a simple approach for implementing them?


50-56: Address remaining success metric

There's one unchecked success metric: "No critical errors". Ensure this is addressed before finalizing the demo.

Are there any known critical errors in the current implementation that need to be fixed?

apps/storefront/app/components/product/ProductOptionSelectorSelect.tsx (6)

2-2: Good addition of price formatting utility

Adding the formatPrice utility is necessary for the enhanced price display functionality.


10-16: Well-designed interface enhancement for pricing information

The updated interface now includes comprehensive pricing properties (minPrice, maxPrice, exactPrice, discountPercentage), enabling more detailed product options display.


20-20: Required addition of currencyCode prop

Adding the currencyCode prop is necessary for proper price formatting and display.


37-43: Good implementation of price-based sorting

Sorting options by price (low to high) provides a consistent and user-friendly display order, improving the shopping experience.


44-71: Comprehensive price display formatting

The implementation handles multiple pricing scenarios elegantly:

  • Single price when min and max are the same
  • Price range display when min and max differ
  • Exact price with optional discount percentage

This enhances the user experience by providing clear pricing information for each option.


77-77:

❓ Verification inconclusive

Verify that default option label is appropriate

The default option label "Select one" is generic. Consider making this more specific to the context, such as "Select size" or "Select color" based on the option type.


🏁 Script executed:

#!/bin/bash
# Find usage of ProductOptionSelectorSelect to understand context
rg -A 5 "ProductOptionSelectorSelect" --type tsx

Length of output: 76


I'll try a different search approach to verify the context of "ProductOptionSelectorSelect." Please run the following shell script:

This should help confirm whether the default option label "Select one" is appropriate for the component's usage context.


🏁 Script executed:

#!/bin/bash
# Search for usage of ProductOptionSelectorSelect specifically in .tsx files and show context for clarity
rg -g "*.tsx" -A 5 "ProductOptionSelectorSelect"

Length of output: 1864


Action: Verify and Potentially Enhance Default Option Label

The default label is currently set as "Select one", which might be too generic for users if the options pertain to specific product attributes (e.g., size or color). Please review the following:

  • Location: apps/storefront/app/components/product/ProductOptionSelectorSelect.tsx (line 77)
  • Consideration: Check if the option prop provides details (such as an attribute name) that can be used to dynamically customize the default label (e.g., "Select size" or "Select color").
  • Next Steps:
    • If such context exists, update the component to use a dynamic/default value reflecting the option type.
    • If no additional context is available or a generic label is intended, please document the rationale for using "Select one".
apps/storefront/app/components/product/ProductOptionSelectorRadio.tsx (5)

5-5: Good addition of price formatting utility

Adding the formatPrice utility is necessary for the enhanced price display functionality.


11-18: Well-designed interface enhancement for pricing information

The updated interface now includes comprehensive pricing properties (minPrice, maxPrice, exactPrice, discountPercentage), maintaining consistency with the Select component while preserving the Radio-specific "disabled" property.


22-22: Required addition of currencyCode prop

Adding the currencyCode prop is necessary for proper price formatting and display.


35-46: Good implementation of filtering and sorting

The component now properly filters unique values and sorts them by price (low to high), providing a consistent and user-friendly display order.


54-117: Well-structured and comprehensive radio option enhancement

The radio option rendering has been significantly improved with:

  • Clear separation of option value and price information
  • Visual indication for unavailable options
  • Proper handling of different pricing scenarios (single price, price range, discount)
  • Consistent styling with visual feedback for selected state

This implementation greatly enhances the user experience when selecting product options.

apps/storefront/app/routes/_index.tsx (11)

23-24: Good color choice for professional branding.

The new overlay color rgba(3, 105, 161, 0.7) with increased opacity creates a more professional and modern look that aligns well with the training platform rebrand.


27-33: Clear brand positioning with good typography hierarchy.

The new heading structure and copy effectively communicate the brand's value proposition. The font combinations (Montserrat and Inter) create a professional look that's appropriate for a training platform.


38-40: Appropriate CTA for the new business model.

The action label and URL path have been updated appropriately to match the new training focus.


43-45: Appropriate hero image selection.

The new image showing people collaborating in a training environment is much more relevant to the rebrand than the previous coffee-themed imagery.


48-75: Well-structured responsive grid layout.

The grid layout with responsive breakpoints is implemented correctly, ensuring the content displays properly on different screen sizes. The image and text are balanced and aligned with the training theme.


90-118: Creative section with good visual hierarchy.

This new section effectively communicates the value proposition with a clear heading structure. The order-switching between mobile and desktop views (using order-1/order-2 classes) is a good responsive design practice.


122-158: Comprehensive course benefits overview.

The ListItems section provides a clear overview of the course benefits, with consistent structure and appropriate iconography. The descriptions are informative and value-focused.


177-201: Effective "Learn & Grow" section with clear steps.

This section provides a clear three-step process for users to understand how the training platform works. The content is well-structured and user-focused.


225-226: Strong visual support for educational narrative.

The image selection enhances the text content and adds credibility to the educational message.


233-249: Compelling educational philosophy content.

The copy effectively communicates the educational approach and value proposition of the training platform, focusing on practical skills and professional development.


264-270: Strong call-to-action section with consistent branding.

The GridCTA component is styled consistently with the rest of the page, maintaining the professional blue color scheme and clear call to action.

apps/medusa/src/scripts/seed.ts (9)

1-2: Good additions of necessary imports.

The additional imports for file system operations and the Link class from modules-sdk are appropriate for the new functionality. The explicit typing of Logger and MedusaContainer enhances type safety.

Also applies to: 24-26


35-37: Improved type safety with explicit typing.

The function signature now explicitly defines the container parameter type as MedusaContainer, which improves type safety and code readability.


125-133: Updated location name and address to match rebrand.

The stock location name and address have been updated to be more generic and aligned with the new branding focus.


140-150: Updated linking methodology using the new Link API.

The code now uses the Link class directly instead of the previous remoteLink approach, aligning with current best practices.


191-198: Consistent use of the Link API for fulfillment set linking.

This change ensures consistency with the earlier Link usage pattern and follows the same best practice.


347-356: Updated product categories to align with training focus.

The new categories (Business, Technical Skills, Leadership) appropriately reflect the training platform's focus areas.


366-382: Clear skill level and feature tags for course filtering.

The updated tags (Beginner, Intermediate, Advanced, Featured, Popular, New) provide useful filtering options for a training platform.


416-456: Added robust error handling for order creation.

The try-catch block for order creation is an excellent improvement that prevents the entire seeding process from failing if a single order creation fails.


459-463: Added validation to skip reviews for products without orders.

This check prevents potential errors when attempting to create reviews for products without any successfully created orders.

apps/medusa/src/scripts/seed/products.ts (2)

4-30: Well-structured interfaces for type safety.

The new interfaces SeedProductsArgs and TrainingProductOptions provide good type safety and make the code more maintainable. The structure clearly defines the expected shape of the data.


109-126: Improved product categorization and tagging.

The function now correctly sets up categories and tags that are relevant to a training platform. The use of optional chaining (?.) when finding category and tag IDs is a good defensive programming practice.

apps/medusa/src/scripts/seed/reviews.ts (15)

5-9: The updated contact details for Sarah Johnson appear valid and consistent.
No issues found.


12-18: New contact info for Michael Chen is clear and matches the rebranding effort.
No issues found.


21-27: Contact record for Emily Rodriguez appears correct.
No issues found.


31-36: Contact details for David Kim are properly updated.
No issues found.


40-45: Lisa Martinez’s information is updated without issues.
No concerns.


54-54: Transition from coffee review to course feedback.
Change is appropriate for the new focus.


58-60: Additional 5-star review lines updated.
All good here as well.


63-65: Course review update is consistent with the branding shift.
LGTM.


69-70: Minor content change to reflect course specifics.
No issues found.


74-74: Review text updated to highlight course materials.
Looks fine.


79-80: “Good course for beginners” - straightforward rebranding.
No concerns.


84-85: New review highlights the course’s practical approach.
Matches the educational theme.


89-90: Review praising the course’s exercises.
No issues noted.


93-94: 5-star rating with real-world examples mention.
Refined for training context.


100-101: Course structure review text revised.
All good here.

apps/storefront/app/templates/ProductTemplate.tsx (11)

38-38: Importing additional pricing utilities is aligned with updated functionality.
No issues here.


48-52: JSDoc comment for getAddToCartValidator is concise and clear.
This enhances readability.


74-78: Well-defined JSDoc for getBreadcrumbs function.
Documentation looks good.


112-116: JSDoc for variantIsSoldOut clarifies the method’s purpose.
Solid addition.


130-130: defaultValues initialization looks correct.
No issues noted.


133-161: Constructing default variant or fallback for option selections.
Logic appears consistent with user experience goals.


173-175: Memoized selectedVariant retrieval is straightforward.
Implementation is correct.


180-189: First option displays all values.
Implementation is appropriate for initial selection scenarios.


190-221: Filtering subsequent options based on current selections.
Approach is logical and user-friendly.


307-310: New useEffect initializes controlled options from default values upon changes.
No immediate issues found.


347-347: Added margin styling in the header section.
Minor UI improvement; looks fine.

apps/storefront/libs/util/products.ts (8)

8-16: Doc comment for getVariantBySelectedOptions clarifies usage.
Logic remains straightforward.


17-27: selectVariantFromMatrixBySelectedOptions includes clear doc and null checks.
Implementation is sound.


38-49: selectDiscountFromVariant properly handles cases with no discount.
Doc comment illustrates the function’s purpose well.


59-75: generateOptionCombinations now has explanatory JSDoc.
Implementation logic is consistent with the rest of the file.


84-99: selectVariantMatrix is enhanced with a doc comment.
Constructing the matrix is clear and coherent.


104-159: getFilteredOptionValues includes detailed filtering logic with comments.
The approach is well-structured, no issues spotted.


161-245: getOptionValuesWithDiscountLabels clarifies final vs. range-based pricing logic.
Comprehensive documentation and logic.


247-271: getVariantFromSelectedOptions properly handles empty selections and matches.
Implementation matches the doc comment.

apps/storefront/libs/config/site/navigation-items.ts (2)

3-36: Navigation structure successfully rebranded to training platform

The header navigation has been comprehensively updated from a coffee-themed store to an educational platform with appropriate categories like "Technical Skills", "Business Courses", and "Leadership Training". The structure is logical and the URLs correctly match their corresponding labels.


38-71: Footer navigation properly aligned with header content

The footer navigation items have been updated to match the new training platform branding. The sort order is sequential, and the content mirrors the important categories from the header navigation, providing consistent user experience throughout the site.

apps/storefront/app/styles/global.css (4)

7-45: Well-structured color scheme for educational platform

The new blue-based color palette is professionally organized with clear semantic naming and helpful comments. The primary blue colors, secondary teal accents, and neutral highlights create a cohesive scheme appropriate for an educational platform.


46-50: Typography simplified to modern, professional font

Good choice replacing multiple fonts with a single, consistent 'Inter' font family. This ensures a clean, professional appearance throughout the application while reducing complexity and potential loading issues from multiple font families.


62-62: Enhanced heading visibility with font-bold

The addition of font-bold to headings improves visual hierarchy and content scanability - an important consideration for educational content where clear structure helps with information retention.


152-186: Background properties properly structured for responsive design

The background property adjustments ensure proper inheritance between default and mobile contexts, maintaining consistent appearance across device sizes.

Comment on lines 38 to 50
const ensureSelectedCartShippingMethod = async (request: Request, cart: StoreCart) => {
const shippingOptions = await fetchShippingOptions(cart.id);

const selectedShippingMethod = cart.shipping_methods?.[0];

const selectedShippingOption =
selectedShippingMethod && shippingOptions.find((option) => option.id === selectedShippingMethod.shipping_option_id);
if (selectedShippingMethod) return;

if (
!selectedShippingMethod || // No shipping method has been selected
!selectedShippingOption // The selected shipping method is no longer available
) {
const cheapestShippingOption = findCheapestShippingOption(shippingOptions);
const shippingOptions = await fetchShippingOptions(cart.id);

if (cheapestShippingOption) {
await setShippingMethod(request, { cartId: cart.id, shippingOptionId: cheapestShippingOption.id });
}
const cheapestShippingOption = findCheapestShippingOption(shippingOptions);

return;
} else if (selectedShippingMethod.amount !== selectedShippingOption.amount) {
await setShippingMethod(request, { cartId: cart.id, shippingOptionId: selectedShippingOption.id });
if (cheapestShippingOption) {
await setShippingMethod(request, { cartId: cart.id, shippingOptionId: cheapestShippingOption.id });
}
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improved shipping method selection logic

The refactored ensureSelectedCartShippingMethod function is more efficient with a clearer flow. The early return when a shipping method is already selected eliminates unnecessary processing.

However, there's a potential issue:

  • The findCheapestShippingOption function assumes shippingOptions is non-empty and may throw an error if the array is empty

Consider adding a check before finding the cheapest option:

  const shippingOptions = await fetchShippingOptions(cart.id);

+  if (!shippingOptions.length) return;
+
  const cheapestShippingOption = findCheapestShippingOption(shippingOptions);

  if (cheapestShippingOption) {
    await setShippingMethod(request, { cartId: cart.id, shippingOptionId: cheapestShippingOption.id });
  }
📝 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
const ensureSelectedCartShippingMethod = async (request: Request, cart: StoreCart) => {
const shippingOptions = await fetchShippingOptions(cart.id);
const selectedShippingMethod = cart.shipping_methods?.[0];
const selectedShippingOption =
selectedShippingMethod && shippingOptions.find((option) => option.id === selectedShippingMethod.shipping_option_id);
if (selectedShippingMethod) return;
if (
!selectedShippingMethod || // No shipping method has been selected
!selectedShippingOption // The selected shipping method is no longer available
) {
const cheapestShippingOption = findCheapestShippingOption(shippingOptions);
const shippingOptions = await fetchShippingOptions(cart.id);
if (cheapestShippingOption) {
await setShippingMethod(request, { cartId: cart.id, shippingOptionId: cheapestShippingOption.id });
}
const cheapestShippingOption = findCheapestShippingOption(shippingOptions);
return;
} else if (selectedShippingMethod.amount !== selectedShippingOption.amount) {
await setShippingMethod(request, { cartId: cart.id, shippingOptionId: selectedShippingOption.id });
if (cheapestShippingOption) {
await setShippingMethod(request, { cartId: cart.id, shippingOptionId: cheapestShippingOption.id });
}
};
const ensureSelectedCartShippingMethod = async (request: Request, cart: StoreCart) => {
const selectedShippingMethod = cart.shipping_methods?.[0];
if (selectedShippingMethod) return;
const shippingOptions = await fetchShippingOptions(cart.id);
if (!shippingOptions.length) return;
const cheapestShippingOption = findCheapestShippingOption(shippingOptions);
if (cheapestShippingOption) {
await setShippingMethod(request, { cartId: cart.id, shippingOptionId: cheapestShippingOption.id });
}
};

Comment on lines +67 to +75
} else if (optionValue.exactPrice !== undefined) {
// Exact price
priceDisplay = formatPrice(optionValue.exactPrice, { currency: currencyCode });

// Format discount if available
if (optionValue.discountPercentage) {
discountDisplay = `${optionValue.discountPercentage}% off`;
}
>
{({ checked }) => (
<Label as="div" className="flex items-center">
<div className="flex-1">
<span className={checked ? 'text-primary-800' : ''}>{optionValue.label}</span>
</div>
{optionValue.disabled && <span className="text-xs text-gray-500">&nbsp;(not available)</span>}
{checked && <CheckCircleIcon className="text-primary-600 h-5 w-5" aria-hidden="true" />}
</Label>
)}
</Radio>
))}
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Ensure consistent pricing display across components

The pricing display logic in both ProductOptionSelectorRadio and ProductOptionSelectorSelect should be identical to maintain a consistent user experience. Verify that both components handle edge cases (like zero prices or missing values) in the same way.


🏁 Script executed:

#!/bin/bash
# Compare the two pricing display implementations
echo "Comparing pricing format logic between components:"
echo "----------------------------------------"
echo "ProductOptionSelectorSelect pricing logic:"
sed -n '49,65p' apps/storefront/app/components/product/ProductOptionSelectorSelect.tsx
echo "----------------------------------------"
echo "ProductOptionSelectorRadio pricing logic:"
sed -n '59,75p' apps/storefront/app/components/product/ProductOptionSelectorRadio.tsx

Length of output: 2305


Action: Standardize Pricing Display Across Product Option Components

We've identified discrepancies in the pricing logic between ProductOptionSelectorSelect and ProductOptionSelectorRadio that could lead to inconsistent user experiences. In the Select component (lines 49–65), the exact price is appended to a label with a prefixed " - " and the discount (if present) is formatted as (${optionValue.discountPercentage}% off). In contrast, the Radio component (lines 59–75) assigns the formatted exact price directly to priceDisplay and the discount to a separate discountDisplay variable without the " - " prefix or parentheses.

Recommendations:

  • Unify formatting: Choose a single formatting style for both components. For example, either always include the " - " prefix and use parentheses for discounts or adjust both components to use separate variables consistently.
  • Handle edge cases uniformly: Confirm that scenarios such as zero prices or missing values are processed in the same way in both components.
  • Consider refactoring: If appropriate, consolidate the pricing display logic into a shared utility or component to minimize discrepancies and ease future maintenance.

jaruesink and others added 4 commits March 14, 2025 15:14
…rmatting and sorting

- Updated ProductOptionSelectorRadio and ProductOptionSelectorSelect components to include price information (minPrice, maxPrice, exactPrice, discountPercentage).
- Implemented sorting of option values by price in both selectors.
- Added currencyCode prop to support dynamic currency formatting.
- Refactored option value filtering to ensure unique values are displayed.
- Enhanced price display logic to show ranges and discounts where applicable.
…etter image display and add key prop in ProductTemplate for unique identification
…lect.tsx


Using the logical OR operator here may wrongly fallback when aPrice is 0. Consider using the nullish coalescing operator (??) as in: 'const aPrice = a.minPrice ?? a.exactPrice ?? 0;' to correctly handle 0 values.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.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: 0

🧹 Nitpick comments (1)
apps/storefront/app/templates/ProductTemplate.tsx (1)

226-263: Consider refactoring duplicated option change handlers.

Both handleOptionChangeBySelect and handleOptionChangeByRadio contain nearly identical logic for handling option changes and resetting subsequent options. Consider extracting the common logic to a separate function to reduce duplication and improve maintainability.

+ // Helper function to handle option changes
+ const handleOptionChange = (optionId: string, newValue: string) => {
+   // Create new options object with the changed option
+   const newOptions = { ...controlledOptions };
+   newOptions[optionId] = newValue;
+ 
+   // Get all option IDs in order
+   const allOptionIds = product.options?.map((option) => option.id) || [];
+ 
+   // Find the index of the changed option
+   const changedOptionIndex = allOptionIds.indexOf(optionId);
+ 
+   // Get all options that come after the changed one
+   const subsequentOptionIds = changedOptionIndex >= 0 ? allOptionIds.slice(changedOptionIndex + 1) : [];
+ 
+   // Reset all subsequent options to their first available value
+   if (subsequentOptionIds.length > 0) {
+     // For each subsequent option, find available values based on current selections
+     subsequentOptionIds.forEach((optionId) => {
+       if (!optionId) return;
+ 
+       // Get filtered option values for this option
+       const filteredValues = getFilteredOptionValues(product, newOptions, optionId);
+ 
+       if (filteredValues.length > 0) {
+         // Set to first available value
+         newOptions[optionId] = filteredValues[0].value;
+       } else {
+         // No valid options, set to empty
+         newOptions[optionId] = '';
+       }
+     });
+   }
+ 
+   setControlledOptions(newOptions);
+ };
+
  const handleOptionChangeBySelect = (e: ChangeEvent<HTMLInputElement>) => {
-   const changedOptionId = e.target.name.replace('options.', '');
-   const newValue = e.target.value;
-
-   // Create new options object with the changed option
-   const newOptions = { ...controlledOptions };
-   newOptions[changedOptionId] = newValue;
-
-   // Get all option IDs in order
-   const allOptionIds = product.options?.map((option) => option.id) || [];
-
-   // Find the index of the changed option
-   const changedOptionIndex = allOptionIds.indexOf(changedOptionId);
-
-   // Get all options that come after the changed one
-   const subsequentOptionIds = changedOptionIndex >= 0 ? allOptionIds.slice(changedOptionIndex + 1) : [];
-
-   // Reset all subsequent options to their first available value
-   if (subsequentOptionIds.length > 0) {
-     // For each subsequent option, find available values based on current selections
-     subsequentOptionIds.forEach((optionId) => {
-       if (!optionId) return;
-
-       // Get filtered option values for this option
-       const filteredValues = getFilteredOptionValues(product, newOptions, optionId);
-
-       if (filteredValues.length > 0) {
-         // Set to first available value
-         newOptions[optionId] = filteredValues[0].value;
-       } else {
-         // No valid options, set to empty
-         newOptions[optionId] = '';
-       }
-     });
-   }
-
-   setControlledOptions(newOptions);
+   const optionId = e.target.name.replace('options.', '');
+   const newValue = e.target.value;
+   handleOptionChange(optionId, newValue);
  };

  const handleOptionChangeByRadio = (name: string, value: string) => {
-   // Create new options object with the changed option
-   const newOptions = { ...controlledOptions };
-   newOptions[name] = value;
-
-   // Get all option IDs in order
-   const allOptionIds = product.options?.map((option) => option.id) || [];
-
-   // Find the index of the changed option
-   const changedOptionIndex = allOptionIds.indexOf(name);
-
-   // Get all options that come after the changed one
-   const subsequentOptionIds = changedOptionIndex >= 0 ? allOptionIds.slice(changedOptionIndex + 1) : [];
-
-   // Reset all subsequent options to their first available value
-   if (subsequentOptionIds.length > 0) {
-     // For each subsequent option, find available values based on current selections
-     subsequentOptionIds.forEach((optionId) => {
-       if (!optionId) return;
-
-       // Get filtered option values for this option
-       const filteredValues = getFilteredOptionValues(product, newOptions, optionId);
-
-       if (filteredValues.length > 0) {
-         // Set to first available value
-         newOptions[optionId] = filteredValues[0].value;
-       } else {
-         // No valid options, set to empty
-         newOptions[optionId] = '';
-       }
-     });
-   }
-
-   setControlledOptions(newOptions);
+   handleOptionChange(name, value);
  };

Also applies to: 265-299

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ebcbff and f92998a.

📒 Files selected for processing (2)
  • apps/storefront/app/components/product/ProductImageGallery.tsx (1 hunks)
  • apps/storefront/app/templates/ProductTemplate.tsx (10 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/storefront/app/components/product/ProductImageGallery.tsx
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: deploy-storefront
  • GitHub Check: deploy-medusa
🔇 Additional comments (11)
apps/storefront/app/templates/ProductTemplate.tsx (11)

38-38: Good addition of utility imports.

Adding utilities for price formatting and variant selection from a central location improves code organization and reusability.


48-52: Documentation improvement with JSDoc comments.

Adding JSDoc comments to the getAddToCartValidator function enhances code readability and maintainability by clearly describing the function's purpose, parameters, and return value.


74-104: Well-implemented breadcrumb navigation function.

The getBreadcrumbs function is well-structured and properly documented with JSDoc comments. It creates a consistent navigation experience by generating breadcrumb links from home to the current product, including collection data when available.


112-119: Clear inventory status check function.

The variantIsSoldOut function with JSDoc documentation provides a clean way to determine if a product variant is sold out based on inventory management settings and quantity.


130-161: Improved default options handling with memoization.

The refactoring of defaultValues.options with useMemo is an excellent improvement that:

  1. Prioritizes the first variant's options as defaults
  2. Falls back to first option values when needed
  3. Prevents unnecessary recalculations
  4. Handles edge cases where variants or options might be undefined

This approach ensures consistent default selections for product options.


173-175: Improved variant selection with memoization.

Using useMemo for selectedVariant calculation ensures that variants are only recalculated when necessary, which improves performance.


180-219: Enhanced product options handling with special case for first option.

The updated product options handling improves the user experience by:

  1. Always showing all values for the first option (Duration)
  2. Adding price information to option values
  3. Filtering subsequent options based on previous selections
  4. Only showing available option combinations

This approach guides users to valid product configurations and provides clearer pricing information.


307-310: Good initialization of controlled options.

Adding a useEffect hook to initialize controlledOptions with defaultValues.options ensures that the component state is properly synchronized with the computed default values, preventing potential inconsistencies.


339-339: Proper React key addition.

Adding a key prop that uses the product ID to the ProductImageGallery component helps React efficiently update the DOM when the product changes.


347-347: UI refinement with added margin.

Adding margin to the header element improves spacing and visual hierarchy in the product display.


394-394: Currency code propagation to option selectors.

Passing the currencyCode prop to the product option selectors enables consistent price formatting throughout the component tree.

Also applies to: 413-413

jaruesink and others added 10 commits March 14, 2025 15:26
…rolledOptions helper function

- Introduced updateControlledOptions function to streamline the logic for updating controlled options based on user selection.
- Refactored handleOptionChangeBySelect and handleOptionChangeByRadio to utilize the new helper function for improved code clarity and maintainability.
- Introduced modular components for CartDrawer: CartDrawerHeader, CartDrawerContent, CartDrawerEmpty, CartDrawerLoading, CartDrawerItems, and CartDrawerFooter for better readability and maintainability.
- Enhanced useCart hook to manage cart state and loading states more effectively, including handling last item removal and drawer toggle logic.
- Added loading and empty states to the CartDrawer for improved user experience.
- Created a design document outlining the architecture and requirements for the cart drawer system.
- Eliminated redundant useEffect that initialized controlledOptions, improving component performance and readability.
- Streamlined the ProductTemplate component by removing outdated code artifacts.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- Removed unnecessary condition from the disabled prop of the checkout button in CartDrawerFooter, enhancing clarity.
- Cleaned up the ProductTemplate component by removing commented-out code, improving overall readability.
feat(cart): refactor CartDrawer component and enhance useCart hook
…logic

feat(product-options): enhance product option selectors with price formatting and sorting
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: 0

🧹 Nitpick comments (6)
apps/storefront/app/components/cart/CartDrawerDesignDoc.md (3)

30-30: Consider adding a comma before “but” to improve readability.

Replacing "Cart is empty but items are being added" with "Cart is empty, but items are being added" helps clarify the sentence.

-4. Cart is empty but items are being added
+4. Cart is empty, but items are being added
🧰 Tools
🪛 LanguageTool

[uncategorized] ~30-~30: Use a comma before ‘but’ if it connects two independent clauses (unless they are closely connected and short).
Context: .... Cart is being updated 4. Cart is empty but items are being added ## First Princip...

(COMMA_COMPOUND_SENTENCE_2)


60-60: Specify a language for the fenced code block.

Markdown linting tools recommend including a language identifier for syntax highlighting and clarity.

-```
+```mermaid
  CartDrawer
  ...
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

60-60: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


75-75: Add a language identifier for the fenced code block.

Same as above: specifying a language enhances readability and consistency with Markdown best practices.

-```
+```mermaid
  useCart
  ...
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

75-75: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

apps/storefront/app/hooks/useCart.ts (1)

96-102: Extract the 1500ms “magic number” into a named constant.

Defining a named constant helps clarify where this duration comes from and why.

 const CLOSE_DRAWER_DELAY_MS = 1500;

 ...
 timerRef.current = window.setTimeout(() => {
   actions.toggleCartDrawer(false);
-}, 1500);
+}, CLOSE_DRAWER_DELAY_MS);
apps/storefront/app/components/cart/CartDrawer.tsx (2)

55-67: Consider defining a type interface for cart items.

Using any[] for items can obscure potential type mismatches. A typed interface (e.g., CartItem) would enhance maintainability.

-type CartDrawerItemsProps = {
-  items: any[];
-  ...
-};

+interface CartItem {
+  id: string;
+  quantity: number;
+  // define other fields as needed
+}
+
+type CartDrawerItemsProps = {
+  items: CartItem[];
+  ...
+};

93-137: Type the cart prop for clarity.

Similarly, using any for the cart prop hides its structure. A proper interface could improve readability and maintainability.

-type CartDrawerFooterProps = {
-  cart: any;
-  ...
-};

+import { StoreCart } from "@medusajs/types";

+interface CartDrawerFooterProps {
+  cart: StoreCart | undefined;
+  ...
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f92998a and 4b3779a.

📒 Files selected for processing (5)
  • apps/storefront/app/components/cart/CartDrawer.tsx (1 hunks)
  • apps/storefront/app/components/cart/CartDrawerDesignDoc.md (1 hunks)
  • apps/storefront/app/components/product/ProductOptionSelectorSelect.tsx (1 hunks)
  • apps/storefront/app/hooks/useCart.ts (1 hunks)
  • apps/storefront/app/templates/ProductTemplate.tsx (13 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/storefront/app/components/product/ProductOptionSelectorSelect.tsx
  • apps/storefront/app/templates/ProductTemplate.tsx
🧰 Additional context used
🪛 LanguageTool
apps/storefront/app/components/cart/CartDrawerDesignDoc.md

[duplication] ~21-~21: Possible typo: you repeated a word.
Context: ... 3. User removes the last item from the cart 4. Cart becomes empty (with animation) ### Loa...

(ENGLISH_WORD_REPEAT_RULE)


[duplication] ~28-~28: Possible typo: you repeated a word.
Context: ...art 2. Items are being removed from the cart 3. Cart is being updated 4. Cart is empty but i...

(ENGLISH_WORD_REPEAT_RULE)


[uncategorized] ~30-~30: Use a comma before ‘but’ if it connects two independent clauses (unless they are closely connected and short).
Context: .... Cart is being updated 4. Cart is empty but items are being added ## First Princip...

(COMMA_COMPOUND_SENTENCE_2)

🪛 markdownlint-cli2 (0.17.2)
apps/storefront/app/components/cart/CartDrawerDesignDoc.md

60-60: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


75-75: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: deploy-storefront
  • GitHub Check: deploy-medusa
🔇 Additional comments (9)
apps/storefront/app/components/cart/CartDrawerDesignDoc.md (3)

21-21: No repeated word found; likely a false positive.

Despite the static analysis hint, there does not appear to be any duplicated words in line 21. You can safely ignore this warning.

🧰 Tools
🪛 LanguageTool

[duplication] ~21-~21: Possible typo: you repeated a word.
Context: ... 3. User removes the last item from the cart 4. Cart becomes empty (with animation) ### Loa...

(ENGLISH_WORD_REPEAT_RULE)


28-28: No repeated word found; likely a false positive.

Similarly, no duplication seems present here. The analyzer’s flagged issue is likely a false positive.

🧰 Tools
🪛 LanguageTool

[duplication] ~28-~28: Possible typo: you repeated a word.
Context: ...art 2. Items are being removed from the cart 3. Cart is being updated 4. Cart is empty but i...

(ENGLISH_WORD_REPEAT_RULE)


1-156: Thorough design documentation!

This file comprehensively explains requirements, state transitions, and best practices for the cart drawer. It fosters clarity and maintainability of the cart system.

🧰 Tools
🪛 LanguageTool

[duplication] ~21-~21: Possible typo: you repeated a word.
Context: ... 3. User removes the last item from the cart 4. Cart becomes empty (with animation) ### Loa...

(ENGLISH_WORD_REPEAT_RULE)


[duplication] ~28-~28: Possible typo: you repeated a word.
Context: ...art 2. Items are being removed from the cart 3. Cart is being updated 4. Cart is empty but i...

(ENGLISH_WORD_REPEAT_RULE)


[uncategorized] ~30-~30: Use a comma before ‘but’ if it connects two independent clauses (unless they are closely connected and short).
Context: .... Cart is being updated 4. Cart is empty but items are being added ## First Princip...

(COMMA_COMPOUND_SENTENCE_2)

🪛 markdownlint-cli2 (0.17.2)

60-60: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


75-75: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

apps/storefront/app/hooks/useCart.ts (4)

5-5: Import usage looks correct.

These React hooks are appropriate for managing local state, effects, and refs.


17-19: Straightforward approach for tracking removal of the last item.

Storing a boolean in state for isRemovingLastItem is concise and effective for controlling the drawer closure logic.


53-58: Neat logic for identifying the last item removal scenario.

The conditions accurately detect when the cart transitions from one item to zero.


129-133: Return object structure is well-organized.

This hook neatly provides the essential cart and UI states. The interface is clear and consistent with the rest of the code.

apps/storefront/app/components/cart/CartDrawer.tsx (2)

14-28: Simple and effective header component.

Segmenting out CartDrawerHeader promotes clarity, and your lightweight pluralization is a nice touch.


139-229: Well-structured modular cart drawer implementation.

Splitting the drawer content into separate components fosters clarity and maintainability. The synchronization with useCart() states is clean.

- Refactored CartDrawerContent to simplify item display logic and ensure accurate rendering of empty cart messages.
- Enhanced useCart hook with improved logic for handling last item removal and drawer state management.
- Added debug logging to assist with troubleshooting cart state issues.
- Updated derived UI states to prevent false empty cart messages during item removal.
- Refactored CartDrawerContent to simplify item display logic and ensure accurate rendering of empty cart messages.
- Enhanced useCart hook with improved logic for handling last item removal and drawer state management.
- Added debug logging to assist with troubleshooting cart state issues.
- Updated derived UI states to prevent false empty cart messages during item removal.
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: 0

🧹 Nitpick comments (1)
apps/storefront/app/components/cart/CartDrawer.tsx (1)

177-185: Remove debug logging before production.

While debug logging is helpful during development, it should be removed or conditionally enabled before deploying to production.

-  // Debug log to help identify issues
-  useEffect(() => {
-    console.log({
-      lineItems,
-      lineItemsTotal,
-      isRemovingLastItem,
-      showEmptyCartMessage,
-      cartDrawerOpen,
-    });
-  }, [lineItems, lineItemsTotal, isRemovingLastItem, showEmptyCartMessage, cartDrawerOpen]);

Consider using a debug utility that can be toggled off in production:

// Alternative approach
const isDev = process.env.NODE_ENV === 'development';
useEffect(() => {
  if (isDev) {
    console.log({
      lineItems,
      lineItemsTotal,
      isRemovingLastItem,
      showEmptyCartMessage,
      cartDrawerOpen,
    });
  }
}, [lineItems, lineItemsTotal, isRemovingLastItem, showEmptyCartMessage, cartDrawerOpen]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b3779a and 162a22f.

📒 Files selected for processing (2)
  • apps/storefront/app/components/cart/CartDrawer.tsx (1 hunks)
  • apps/storefront/app/hooks/useCart.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: deploy-storefront
  • GitHub Check: deploy-medusa
🔇 Additional comments (21)
apps/storefront/app/hooks/useCart.ts (13)

5-5: The new import organization looks good.

Added imports for React hooks align well with the enhanced state management being implemented in this hook.


7-11: Clear and informative JSDoc comments.

The added documentation clearly explains the purpose and usage of the hook, making it more maintainable and easier for other developers to understand.


17-22: Good addition of state tracking and refs.

The new state variable isRemovingLastItem and refs for timers and previous item count are excellent for managing the lifecycle of cart operations and preventing UI flicker.


24-42: Well-structured fetcher organization.

Reorganizing the fetchers into a structured object with clear properties for adding and removing items improves code readability and maintainability.


44-58: Improved last item removal detection logic.

The enhanced logic for detecting when the last item is being removed is robust, considering both current and previous state to handle edge cases properly.


59-65: Good timer management with proper cleanup.

Adding a dedicated function for timer management with useCallback is a good practice for preventing memory leaks and ensuring consistent behavior.


67-80: Optimized cart drawer toggle function.

The enhanced toggle function now only updates state when necessary and includes proper cleanup, which will prevent unnecessary re-renders.


82-105: Comprehensive effect for cart state transitions.

This effect properly handles cart state transitions, including the special case of removing the last item with appropriate timers and cleanup.


107-118: Sensible auto-close behavior for empty cart.

This effect intelligently closes the drawer when the cart becomes empty, but only when it's not in the middle of animations or operations.


120-125: Proper reset of states for cleaner UX.

Resetting the removing state when the cart is closed or items are added prevents stale states and improves user experience.


127-130: Clean effect cleanup on unmount.

Ensuring timer cleanup on component unmount prevents memory leaks and is a best practice for React hooks.


132-136: Improved empty cart detection.

The logic for determining when to show the empty cart message is comprehensive and considers all relevant states to prevent UI flicker.


138-146: Streamlined return object with necessary states.

The updated return object includes all the states and functions needed by consumers while removing unnecessary details, which is a clean API design.

apps/storefront/app/components/cart/CartDrawer.tsx (8)

1-12: Improved imports and component structure.

Updated imports align with the new component structure and dependencies required for the enhanced cart drawer functionality.


13-28: Well-structured CartDrawerHeader component.

Breaking out the header into its own component with proper props improves code organization and reusability.


30-31: Clean CartDrawerEmpty component.

This simple component for the empty state follows the single responsibility principle and improves maintainability.


33-53: Detailed loading skeleton implementation.

The loading component uses a well-designed skeleton UI pattern with animation, providing excellent visual feedback during loading states.


55-66: Effective CartDrawerItems component.

This component cleanly handles rendering the list of items and properly passes down the removal state to individual items.


68-96: Robust CartDrawerContent component with improved state handling.

The content component effectively manages different states (empty, loading, items) with clear conditional rendering logic.


98-142: Well-implemented CartDrawerFooter component.

The footer component properly handles the checkout button state, subtotal display, and navigation options based on the cart state.


196-243: Improved Dialog implementation with transitions.

The updated Dialog implementation with DialogBackdrop and DialogPanel components provides better structure and smoother transitions for the cart drawer.

- Updated CartDrawerContent to ensure accurate item display and empty cart messaging.
- Simplified conditions for rendering items and empty cart state.
- Removed unnecessary useEffect from useCart hook to streamline drawer closing logic, improving performance and readability.
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: 0

🧹 Nitpick comments (5)
apps/storefront/app/hooks/useCart.ts (2)

53-57: Consider simplifying the complex last item removal logic

The condition for detecting when the last item is being removed is quite complex and might be difficult to maintain.

Consider extracting this logic into a named function with appropriate comments for better readability:

-  // Check if we're removing the last item - improved logic
-  const isLastItemBeingRemoved =
-    isRemovingItemId &&
-    ((itemCount === 1 && isRemovingItemId === lineItems[0]?.id) ||
-      (itemCount === 0 && prevItemCountRef.current === 1 && cartFetchers.removing));
+  // Check if we're removing the last item - improved logic
+  const isLastItemBeingRemoved = checkIfRemovingLastItem(isRemovingItemId, itemCount, lineItems, prevItemCountRef.current, cartFetchers.removing);
+
+  // Helper to determine if we're removing the last item
+  function checkIfRemovingLastItem(
+    removingItemId: string | undefined,
+    currentItemCount: number,
+    items: any[],
+    prevItemCount: number,
+    removingFetcher: any
+  ): boolean {
+    if (!removingItemId) return false;
+    
+    // Case 1: We have one item and it's being removed
+    const isRemovingOnlyItem = currentItemCount === 1 && removingItemId === items[0]?.id;
+    
+    // Case 2: Cart is already empty, but we just removed the last item
+    const justRemovedLastItem = currentItemCount === 0 && prevItemCount === 1 && removingFetcher;
+    
+    return isRemovingOnlyItem || justRemovedLastItem;
+  }

82-105: Consider breaking down the complex effect

This effect handles multiple responsibilities: clearing timers, tracking cart state transitions, handling last item removal, and updating references.

Consider splitting this into smaller, more focused effects for better maintainability. For example:

-  // Effect: Handle cart state transitions
-  useEffect(() => {
-    clearTimer();
-
-    const cartDrawerOpen = !!state.cart.open;
-
-    // Track if we're removing the last item
-    if (isLastItemBeingRemoved && !isRemovingLastItem) {
-      setIsRemovingLastItem(true);
-
-      // When removing the last item, set a timer to close the drawer
-      if (cartDrawerOpen) {
-        timerRef.current = window.setTimeout(() => {
-          actions.toggleCartDrawer(false);
-          setIsRemovingLastItem(false);
-        }, 1500);
-      }
-    }
-
-    // Keep track of previous item count
-    prevItemCountRef.current = itemCount;
-
-    return clearTimer;
-  }, [state.cart.open, itemCount, isLastItemBeingRemoved, isRemovingLastItem, actions, clearTimer]);
+  // Effect: Handle last item removal
+  useEffect(() => {
+    if (isLastItemBeingRemoved && !isRemovingLastItem) {
+      clearTimer();
+      setIsRemovingLastItem(true);
+      
+      if (state.cart.open) {
+        timerRef.current = window.setTimeout(() => {
+          actions.toggleCartDrawer(false);
+          setIsRemovingLastItem(false);
+        }, 1500);
+      }
+    }
+    
+    return clearTimer;
+  }, [isLastItemBeingRemoved, isRemovingLastItem, state.cart.open, actions, clearTimer]);
+  
+  // Effect: Track previous item count
+  useEffect(() => {
+    prevItemCountRef.current = itemCount;
+  }, [itemCount]);
apps/storefront/app/components/cart/CartDrawer.tsx (3)

175-183: Remove debug console.log statements before production

Debug logging statements should not be included in production code as they can impact performance and expose implementation details.

-  // Debug log to help identify issues
-  useEffect(() => {
-    console.log({
-      lineItems,
-      lineItemsTotal,
-      isRemovingLastItem,
-      showEmptyCartMessage,
-      cartDrawerOpen,
-    });
-  }, [lineItems, lineItemsTotal, isRemovingLastItem, showEmptyCartMessage, cartDrawerOpen]);

163-169: Consider simplifying state management

You're maintaining local state (isOpen) that mirrors the external state (cartDrawerOpen), which adds complexity.

Since the Dialog component from Headless UI accepts an open prop directly, consider using cartDrawerOpen directly:

-  // Local state to control the dialog - initialize with cartDrawerOpen
-  const [isOpen, setIsOpen] = useState(false);
-
-  // Sync our local state with the cart drawer state
-  useEffect(() => {
-    setIsOpen(cartDrawerOpen === true);
-  }, [cartDrawerOpen]);

// Then in the render method:
-    <Dialog open={isOpen} onClose={handleClose} className="relative z-50">
+    <Dialog open={cartDrawerOpen === true} onClose={handleClose} className="relative z-50">

This eliminates the need for additional state and the synchronization effect.


142-193: Add proper TypeScript interface for props

While the component has been refactored to use FC (FunctionComponent), it doesn't have a proper TypeScript interface for its props.

Consider adding a proper interface:

interface CartDrawerProps {
  // Add any props if needed in the future
}

export const CartDrawer: FC<CartDrawerProps> = () => {
  // component implementation
};

This will make the code more maintainable and type-safe if props are added in the future.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 162a22f and 6835190.

📒 Files selected for processing (2)
  • apps/storefront/app/components/cart/CartDrawer.tsx (1 hunks)
  • apps/storefront/app/hooks/useCart.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: deploy-medusa
  • GitHub Check: deploy-storefront
🔇 Additional comments (12)
apps/storefront/app/hooks/useCart.ts (7)

7-11: Excellent documentation with JSDoc comments

The added JSDoc comments explain the purpose and return values of the hook, making it easier for other developers to understand and use.


17-23: Good state management approach with useState and useRef

Using useState for UI state and useRef for timers and tracking previous values follows React best practices. The refs effectively prevent memory leaks and unnecessary re-renders.


24-42: Improved fetcher tracking with better organization

Refactoring cart-related fetchers into an object with dedicated properties for removing and adding operations enhances code readability and maintainability.


67-80: Efficient cart drawer toggle implementation

Good optimization by only updating the state when it's actually changing. This prevents unnecessary re-renders and improves performance.


114-117: Good cleanup practice

Properly cleaning up timers on component unmount is important to prevent memory leaks. This follows React best practices.


119-120: Clear and concise derived state logic

The logic for determining when to show the empty cart message is well thought out, considering all possible states to prevent false empty states.


122-133: Well-structured return object

The return object includes all necessary cart data, state, and methods with descriptive variable names for clarity.

apps/storefront/app/components/cart/CartDrawer.tsx (5)

13-28: Good component composition with CartDrawerHeader

Breaking down the UI into smaller, focused components improves maintainability and readability. The props are well-defined and the component is properly typed.


30-53: Well-implemented empty and loading state components

The empty and loading state components provide clear visual feedback to users. The skeleton loading UI is a good practice for improving perceived performance.


55-94: Well-structured ItemList and Content components

Breaking the cart drawer into modular components with clear responsibilities improves code organization. The conditional rendering logic in CartDrawerContent is clear and handles all possible states.


96-140: Well-implemented CartDrawerFooter component

The footer component is well-structured with appropriate props and conditional logic. The checkout button is properly disabled when there are no items in the cart.


194-240: Improved dialog structure with Headless UI components

The use of DialogBackdrop and DialogPanel with transitions provides a better user experience and follows accessibility best practices. The structure is clean and well-organized.

…tional rendering

- Updated CartDrawerLoading to accept a className prop for better styling flexibility.
- Simplified conditional rendering logic in CartDrawerContent for loading state, ensuring consistent spacing based on item presence.
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: 0

🧹 Nitpick comments (2)
apps/storefront/app/components/cart/CartDrawer.tsx (2)

56-67: Type safety could be improved in component props

While the component modularization is excellent, using any[] for items reduces type safety.

- items: any[];
+ items: {
+   id: string;
+   quantity: number;
+   // Add other relevant cart item properties
+ }[];

Also, the comment on line 79 could be removed as the code is self-explanatory.

Also applies to: 69-95


175-184: Remove debug logging before production deployment

The debug logging should be removed before deploying to production as it will clutter the console and may impact performance.

- // Debug log to help identify issues
- useEffect(() => {
-   console.log({
-     lineItems,
-     lineItemsTotal,
-     isRemovingLastItem,
-     showEmptyCartMessage,
-     cartDrawerOpen,
-   });
- }, [lineItems, lineItemsTotal, isRemovingLastItem, showEmptyCartMessage, cartDrawerOpen]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6835190 and 2f96ef9.

📒 Files selected for processing (1)
  • apps/storefront/app/components/cart/CartDrawer.tsx (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: deploy-storefront
  • GitHub Check: deploy-medusa
🔇 Additional comments (9)
apps/storefront/app/components/cart/CartDrawer.tsx (9)

1-13: Excellent refactoring of imports and dependencies!

The updated imports and organization set the stage for a more modular component structure. Adding useCallback, useState, and the Dialog components from Headlessui creates a solid foundation for the performance improvements and component restructuring.


14-29: Well-structured header component with proper accessibility considerations

The CartDrawerHeader component is nicely modularized with appropriate props typing. Good job implementing accessibility features like aria-label on the close button.


31-54: Good implementation of empty and loading states

The dedicated components for empty and loading states improve UX by providing clear feedback. The loading skeleton implementation with animation and placeholder elements is particularly well done.


97-141: Well-structured footer component with appropriate disabled state handling

The CartDrawerFooter component elegantly handles price formatting and checkout button states. Good job separating this into its own component.


158-162: Effective approach to tracking cart loading state

Using useFetchers() to track cart-related operations in progress is an elegant solution that ensures the UI stays in sync with background operations.


164-170: Good state synchronization pattern

The local state with useEffect synchronization creates a clean separation between internal component state and external state from the cart hook.


186-193: Excellent use of useCallback for event handlers

Using useCallback for the checkout and close handlers is a great optimization that prevents unnecessary re-renders. This is especially important in modal components that might trigger frequent state changes.


196-210: Great implementation of DialogBackdrop and DialogPanel with transitions

The updated Dialog implementation with separate BackDrop and Panel components gives you more control over animations and styling. The use of data attributes for transition states is particularly elegant.


212-235: Clear organization of drawer content and footer

The separation of content and footer in the drawer layout creates a clear structure that's easy to maintain. The appropriate props are passed to each component, ensuring they have the data they need.

- Added navigatingToCheckout state to manage button text and disabled state during checkout.
- Simplified checkout button logic to improve user experience and clarity.
- Removed unnecessary debug logging to clean up the CartDrawer component.
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: 0

🧹 Nitpick comments (4)
apps/storefront/app/components/cart/CartDrawer.tsx (4)

56-67: Consider adding a type for cart items.

While the CartDrawerItems component is well-implemented, the items prop is typed as any[]. It would improve type safety to define a specific interface for cart items.

-const CartDrawerItems: FC<{
-  items: any[];
+const CartDrawerItems: FC<{
+  items: CartItem[];
  isRemovingItemId?: string;
  currencyCode: string;
}> = ({ items, isRemovingItemId, currencyCode }) => (

And define the CartItem interface:

interface CartItem {
  id: string;
  quantity: number;
  // Add other properties as needed
}

69-95: Unused prop and type improvement needed.

The CartDrawerContent component has an unused prop isRemovingLastItem that's passed in but never used within the component. Also, similar to the previous component, the items prop should have a more specific type than any[].

-const CartDrawerContent: FC<{
-  items: any[];
+const CartDrawerContent: FC<{
+  items: CartItem[];
  isRemovingItemId?: string;
  isAddingItem: boolean;
  showEmptyCartMessage: boolean;
-  isRemovingLastItem: boolean;
  currencyCode: string;
-}> = ({ items, isRemovingItemId, isAddingItem, showEmptyCartMessage, isRemovingLastItem, currencyCode }) => {
+}> = ({ items, isRemovingItemId, isAddingItem, showEmptyCartMessage, currencyCode }) => {

If the isRemovingLastItem prop is actually needed, make sure it's used within the component.


97-140: Improved CartDrawerFooter component with proper state handling.

The footer component is well-structured but could benefit from a more specific type for the cart prop rather than using any.

-  cart: any;
+  cart: {
+    items: CartItem[];
+    // Add other properties as needed
+  } | null;

175-182: Consider using a more reliable method than setTimeout for navigation completion.

Using a hardcoded timeout of 750ms to reset states after navigation might not be reliable if the navigation takes longer than expected.

  const handleCheckoutClick = useCallback(() => {
    setNavigatingToCheckout(true);
    navigate('/checkout');
-   setTimeout(() => {
-     toggleCartDrawer(false);
-     setNavigatingToCheckout(false);
-   }, 750);
+   // Use the useEffect cleanup to handle this more reliably
  }, [navigate, toggleCartDrawer]);
+
+ // Add this effect to handle navigation completion
+ useEffect(() => {
+   // When navigating away, cleanup the cart drawer state
+   return () => {
+     if (navigatingToCheckout) {
+       toggleCartDrawer(false);
+       setNavigatingToCheckout(false);
+     }
+   };
+ }, [navigatingToCheckout, toggleCartDrawer]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f96ef9 and a2068d0.

📒 Files selected for processing (1)
  • apps/storefront/app/components/cart/CartDrawer.tsx (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: deploy-medusa
  • GitHub Check: deploy-storefront
🔇 Additional comments (8)
apps/storefront/app/components/cart/CartDrawer.tsx (8)

1-12: Import statements look well-structured and appropriate.

The updated imports align well with the refactoring performed in this file. The addition of React hooks (useCallback, useState, useEffect) and headless UI components improves the organization of the code and provides better state management.


14-29: Good component extraction for CartDrawerHeader.

This component is well-designed with properly typed props and good accessibility features. The DialogTitle component ensures proper a11y, and the close button has an appropriate aria-label.


31-32: Clean implementation of CartDrawerEmpty component.

This simple component follows the single responsibility principle and provides a consistent empty state message.


34-54: Excellent loading state with skeleton UI.

The CartDrawerLoading component provides a good user experience with the animate-pulse effect and a layout that matches the actual content structure. The component allows for customization via the optional className prop.


158-162: Well-implemented cart loading detection logic.

The logic for detecting when cart-related operations are in progress is well-implemented, checking both the fetcher state and the action or form data to determine if it's a cart operation.


164-170: Good state synchronization between local and cart drawer state.

Using local state with useEffect to synchronize with the cartDrawerOpen state from the useCart hook ensures that the component stays in sync with the application state while maintaining control over its own rendering.


189-233: Improved Dialog implementation with transitions.

The use of Headless UI's Dialog components with transitions provides a better user experience. The separation of DialogBackdrop and DialogPanel with appropriate transition classes creates a smooth animation effect.


207-227: Good component composition.

The main CartDrawer component effectively composes the smaller, specialized components to create a clean, modular structure. This approach improves maintainability and readability.

…nents

- Updated GridCTA and IndexRoute components to use a consistent rounded-lg class for border radius.
- Enhanced visual consistency in UI elements by aligning styling across different sections.
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: 0

🧹 Nitpick comments (3)
apps/storefront/app/routes/_index.tsx (3)

23-44: Good job on the rebranding for the main Hero component.

The transformation from coffee-themed to training-focused content is well executed with appropriate changes to text, colors, and imagery. The content effectively communicates the new brand identity.

However, I notice you're using external Unsplash URLs for images:

-  url: 'https://images.unsplash.com/photo-1522202176988-66273c2fd55f?w=800&auto=format&fit=crop&q=60',
+  url: '/assets/images/hero-training.jpg',

Consider downloading and hosting these images locally for better performance, reliability, and to avoid potential issues if the external URL becomes unavailable.


80-86: Consider using theme variables for overlay colors.

Instead of hardcoding RGBA values, consider using theme variables for consistent styling across the application.

- overlayColor="rgba(15, 118, 110, 0.6)"
+ overlayColor="var(--theme-primary-overlay)"

173-215: Well-executed learning path section.

The Hero component has been effectively repurposed to showcase the learning journey. The three-step process is clear and compelling.

However, the same external image URL is used here as in the first Hero component:

- url: 'https://images.unsplash.com/photo-1522202176988-66273c2fd55f?w=800&auto=format&fit=crop&q=60',
+ url: '/assets/images/hero-training.jpg',

Consider using a different image for visual variety, or at minimum, reference the same local asset to avoid duplication.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2068d0 and 7cb548b.

📒 Files selected for processing (2)
  • apps/storefront/app/components/sections/GridCTA.tsx (1 hunks)
  • apps/storefront/app/routes/_index.tsx (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/storefront/app/components/sections/GridCTA.tsx
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: deploy-storefront
  • GitHub Check: deploy-medusa
🔇 Additional comments (6)
apps/storefront/app/routes/_index.tsx (6)

48-75: Great addition of the "Building Careers" section.

This new section effectively reinforces the training platform brand with well-structured content and appropriate imagery. The grid layout works well for responsive design.


90-118: Well-structured "Art of Learning" section.

The section is visually balanced with good responsive ordering (note the order-1/order-2 classes). The content effectively communicates the value proposition of the training platform.


122-156: Effective conversion of benefits section to course features.

The ListItems component has been updated with relevant training-focused content while maintaining the existing structure. The descriptions are clear and compelling.


162-162: Good update to product list heading.

Changing from "Our Blends" to "Featured Courses" maintains consistency with the training theme.


221-249: Excellent description of the educational approach.

The content effectively communicates the training methodology and philosophy. The SideBySide component is well utilized for this purpose.


256-279: Strong call-to-action section with consistent branding.

The GridCTA component effectively reinforces the brand identity and encourages community engagement. The styling is consistent with the rest of the page.

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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

🛑 Comments failed to post (64)
apps/medusa/src/api/mcp/servers/medusa/tools/registry.ts (1)

35-35: ⚠️ Potential issue

Incomplete feature in import path

The import path contains "TODO (not working yet)" which indicates this feature is incomplete. Consider moving this import to a separate file for work-in-progress features or add a more descriptive comment explaining what needs to be fixed.

apps/medusa/src/api/mcp/middlewares.ts (2)

15-22: 🛠️ Refactor suggestion

Improve the logRequest middleware to filter sensitive information

The current logging middleware logs the entire request body without any filtering of sensitive information. This could potentially expose sensitive data in logs.

Consider implementing filtering for sensitive fields before logging:

const logRequest = (req: MedusaRequest, res: MedusaResponse, next: NextFunction) => {
+  const filterSensitiveData = (obj: any): any => {
+    if (!obj) return obj;
+    const filtered = { ...obj };
+    const sensitiveFields = ['password', 'token', 'credit_card', 'api_key'];
+    
+    for (const key in filtered) {
+      if (sensitiveFields.includes(key.toLowerCase())) {
+        filtered[key] = '[REDACTED]';
+      } else if (typeof filtered[key] === 'object') {
+        filtered[key] = filterSensitiveData(filtered[key]);
+      }
+    }
+    return filtered;
+  };
+
  console.log(`MCP ${req.method} ${req.path}`, {
    query: req.query,
-    body: req.body,
+    body: filterSensitiveData(req.body),
  });
  next();
};
📝 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.

// Request logging middleware
const logRequest = (req: MedusaRequest, res: MedusaResponse, next: NextFunction) => {
  const filterSensitiveData = (obj: any): any => {
    if (!obj) return obj;
    const filtered = { ...obj };
    const sensitiveFields = ['password', 'token', 'credit_card', 'api_key'];
    
    for (const key in filtered) {
      if (sensitiveFields.includes(key.toLowerCase())) {
        filtered[key] = '[REDACTED]';
      } else if (typeof filtered[key] === 'object') {
        filtered[key] = filterSensitiveData(filtered[key]);
      }
    }
    return filtered;
  };

  console.log(`MCP ${req.method} ${req.path}`, {
    query: req.query,
    body: filterSensitiveData(req.body),
  });
  next();
};

24-36: 🛠️ Refactor suggestion

Consider adding authentication middleware for secure endpoints

The current middleware setup doesn't include any authentication or authorization checks for the endpoints, which could lead to unauthorized access.

Consider adding authentication middleware to secure these endpoints:

import type { NextFunction } from 'express';
import cors from 'cors';
import { json } from 'express';
import { MedusaRequest, MedusaResponse, type MiddlewareRoute } from '@medusajs/framework';
+ import { authenticate } from 'path-to-your-auth-middleware';

// Define middleware for SSE endpoint
export const storeProductMcpRoutesMiddlewares: MiddlewareRoute[] = [
  {
    matcher: '/mcp/sse',
    method: 'GET',
-    middlewares: [cors(), sseHeaders, logRequest],
+    middlewares: [cors(), authenticate, sseHeaders, logRequest],
  },
  {
    matcher: '/mcp/messages',
    method: 'POST',
-    middlewares: [cors(), json(), logRequest],
+    middlewares: [cors(), json(), authenticate, logRequest],
  },
];
📝 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.

import type { NextFunction } from 'express';
import cors from 'cors';
import { json } from 'express';
import { MedusaRequest, MedusaResponse, type MiddlewareRoute } from '@medusajs/framework';
import { authenticate } from 'path-to-your-auth-middleware';

// Define middleware for SSE endpoint
export const storeProductMcpRoutesMiddlewares: MiddlewareRoute[] = [
  {
    matcher: '/mcp/sse',
    method: 'GET',
    middlewares: [cors(), authenticate, sseHeaders, logRequest],
  },
  {
    matcher: '/mcp/messages',
    method: 'POST',
    middlewares: [cors(), json(), authenticate, logRequest],
  },
];
apps/medusa/src/api/mcp/servers/medusa/tools/products/product-tools.helpers.ts (2)

124-132: ⚠️ Potential issue

Inconsistent variant handling between update and create functions

The transformProductForUpdate function transforms variants using the transformVariant function, but transformProductForCreate passes them through without transformation.

Apply this transformation to ensure consistent behavior:

export const transformProductForCreate = (productInput: ProductInput): CreateProductWorkflowInputDTO => {
  const commonFields = transformCommonProductFields(productInput);
  return {
    ...commonFields,
    title: productInput.title,
    shipping_profile_id: commonFields.shipping_profile_id as string,
-    variants: productInput.variants,
+    variants: productInput.variants?.map((v) => 
+      transformVariant(v as ProductVariant & { id?: string }, productInput.options)
+    ),
  };
};
📝 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.

export const transformProductForCreate = (productInput: ProductInput): CreateProductWorkflowInputDTO => {
  const commonFields = transformCommonProductFields(productInput);
  return {
    ...commonFields,
    title: productInput.title,
    shipping_profile_id: commonFields.shipping_profile_id as string,
    variants: productInput.variants?.map((v) => 
      transformVariant(v as ProductVariant & { id?: string }, productInput.options)
    ),
  };
};

129-129: ⚠️ Potential issue

Add validation for shipping_profile_id before type casting

The code casts commonFields.shipping_profile_id to string without validation, which could lead to runtime errors if it's undefined.

export const transformProductForCreate = (productInput: ProductInput): CreateProductWorkflowInputDTO => {
  const commonFields = transformCommonProductFields(productInput);
+  
+  if (!commonFields.shipping_profile_id) {
+    throw new Error("shipping_profile_id is required for product creation");
+  }
+  
  return {
    ...commonFields,
    title: productInput.title,
    shipping_profile_id: commonFields.shipping_profile_id as string,
    variants: productInput.variants,
  };
};
📝 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.

export const transformProductForCreate = (productInput: ProductInput): CreateProductWorkflowInputDTO => {
  const commonFields = transformCommonProductFields(productInput);
  
  if (!commonFields.shipping_profile_id) {
    throw new Error("shipping_profile_id is required for product creation");
  }
  
  return {
    ...commonFields,
    title: productInput.title,
    shipping_profile_id: commonFields.shipping_profile_id as string,
    variants: productInput.variants,
  };
};
apps/medusa/src/api/mcp/servers/medusa/docs/tools/product/updating-products with array (not working).md (2)

9-42: 🛠️ Refactor suggestion

Document the second update method

The overview mentions two methods for updating products: "by specifying a list of products or by using a selector to filter products," but only the first method is documented. This leaves the documentation incomplete.

Add a "2. Update by Selector" section that includes:

  • Description of the selector approach
  • Code example showing how to use a selector
  • Explanation of selector parameters

1-1: ⚠️ Potential issue

Remove "(not working)" from the filename

The filename contains "(not working)" which suggests this is either a draft or contains a known issue. If this documentation is meant for production use, the filename should be cleaned up.

Rename the file to "updating-products.md" if the documented approach is now working correctly.

apps/medusa/src/api/mcp/servers/medusa/tools/categories/category-tools.helpers.ts (1)

3-7: 🛠️ Refactor suggestion

Consider handling edge cases in handle generation

The generateUniqueHandle function has some potential edge cases:

  • Empty name inputs could result in handles with just a timestamp
  • Names with only special characters would result in just a hyphen and timestamp
  • No maximum length constraint could create overly long handles
 const generateUniqueHandle = (name: string) => {
+  if (!name || name.trim() === '') {
+    return `category-${Date.now()}`;
+  }
   const timestamp = Date.now();
   const baseHandle = name.toLowerCase().replace(/[^a-z0-9]+/g, '-');
-  return `${baseHandle}-${timestamp}`;
+  // Remove leading/trailing hyphens and limit length
+  const sanitizedHandle = baseHandle.replace(/^-+|-+$/g, '');
+  const truncatedHandle = sanitizedHandle.substring(0, 50); // Reasonable max length
+  return truncatedHandle ? `${truncatedHandle}-${timestamp}` : `category-${timestamp}`;
 };
📝 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.

const generateUniqueHandle = (name: string) => {
  if (!name || name.trim() === '') {
    return `category-${Date.now()}`;
  }
  const timestamp = Date.now();
  const baseHandle = name.toLowerCase().replace(/[^a-z0-9]+/g, '-');
  // Remove leading/trailing hyphens and limit length
  const sanitizedHandle = baseHandle.replace(/^-+|-+$/g, '');
  const truncatedHandle = sanitizedHandle.substring(0, 50); // Reasonable max length
  return truncatedHandle ? `${truncatedHandle}-${timestamp}` : `category-${timestamp}`;
};
apps/medusa/src/api/mcp/sse/route.ts (5)

76-88: 🛠️ Refactor suggestion

Update utility functions to work with the connection manager

With the introduction of the connection manager pattern, update the utility functions to interact with it.

 // Export function to get the active transport
-export function getActiveTransport(): SSEServerTransport | null {
-  return activeTransport;
+export function getActiveTransport(sessionId: string): SSEServerTransport | undefined {
+  return connectionManager.getConnection(sessionId);
 }
 
 // Export function to get/set the active session ID
-export function getActiveSessionId(): string | null {
-  return activeSessionId;
+export function getActiveSessions(): string[] {
+  return connectionManager.getAllSessions();
 }
 
-export function setActiveSessionId(sessionId: string): void {
-  activeSessionId = sessionId;
+export function closeSession(sessionId: string): void {
+  connectionManager.closeConnection(sessionId);
 }
📝 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.

// Export function to get the active transport
export function getActiveTransport(sessionId: string): SSEServerTransport | undefined {
  return connectionManager.getConnection(sessionId);
}

// Export function to get/set the active session ID
export function getActiveSessions(): string[] {
  return connectionManager.getAllSessions();
}

export function closeSession(sessionId: string): void {
  connectionManager.closeConnection(sessionId);
}

55-63: 🛠️ Refactor suggestion

Update error handling to use the connection manager pattern

Similar to the previous comments, update the error handler to use the logger and the connection manager pattern.

     // Handle errors
     req.on('error', (error) => {
-      console.error(`SSE connection error for session ${sessionId}:`, error);
-      if (activeTransport === transport && activeSessionId === sessionId) {
-        console.log('Cleaning up transport after error');
-        activeTransport = null;
-        activeSessionId = null;
-      }
-      transport.close();
+      const logger = req.scope.resolve('logger');
+      logger.error(`SSE connection error for session ${sessionId}:`, error);
+      connectionManager.closeConnection(sessionId);
     });
📝 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.

    req.on('error', (error) => {
      const logger = req.scope.resolve('logger');
      logger.error(`SSE connection error for session ${sessionId}:`, error);
      connectionManager.closeConnection(sessionId);
    });

17-18: 🛠️ Refactor suggestion

Add validation for the session ID parameter

The code doesn't validate if the sessionId is provided or if it's a valid string format, which could lead to unexpected behavior.

     // Get the session ID from the query params
     const sessionId = req.query.sessionId as string;
+    
+    // Validate sessionId
+    if (!sessionId || typeof sessionId !== 'string') {
+      throw new Error('Invalid or missing sessionId parameter');
+    }
📝 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.

    // Get the session ID from the query params
    const sessionId = req.query.sessionId as string;
    
    // Validate sessionId
    if (!sessionId || typeof sessionId !== 'string') {
      throw new Error('Invalid or missing sessionId parameter');
    }

6-8: ⚠️ Potential issue

Avoid using global variables for storing connection state

Using global variables for tracking active connections can lead to issues in a production environment, especially with multiple concurrent connections or when running in a clustered/horizontally scaled setup.

Consider implementing a connection manager class or using a more robust state management solution:

-// Store the active transport and its session ID
-let activeTransport: SSEServerTransport | null = null;
-let activeSessionId: string | null = null;
+// Connection manager for SSE transports
+class SSEConnectionManager {
+  private connections: Map<string, SSEServerTransport> = new Map();
+
+  public addConnection(sessionId: string, transport: SSEServerTransport): void {
+    this.closeConnection(sessionId);
+    this.connections.set(sessionId, transport);
+  }
+
+  public getConnection(sessionId: string): SSEServerTransport | undefined {
+    return this.connections.get(sessionId);
+  }
+
+  public closeConnection(sessionId: string): void {
+    const transport = this.connections.get(sessionId);
+    if (transport) {
+      transport.close();
+      this.connections.delete(sessionId);
+    }
+  }
+
+  public getAllSessions(): string[] {
+    return Array.from(this.connections.keys());
+  }
+}
+
+const connectionManager = new SSEConnectionManager();
📝 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.

// Connection manager for SSE transports
class SSEConnectionManager {
  private connections: Map<string, SSEServerTransport> = new Map();

  public addConnection(sessionId: string, transport: SSEServerTransport): void {
    this.closeConnection(sessionId);
    this.connections.set(sessionId, transport);
  }

  public getConnection(sessionId: string): SSEServerTransport | undefined {
    return this.connections.get(sessionId);
  }

  public closeConnection(sessionId: string): void {
    const transport = this.connections.get(sessionId);
    if (transport) {
      transport.close();
      this.connections.delete(sessionId);
    }
  }

  public getAllSessions(): string[] {
    return Array.from(this.connections.keys());
  }
}

const connectionManager = new SSEConnectionManager();

44-52: 🛠️ Refactor suggestion

Update connection cleanup with the connection manager pattern

Similar to the previous comment about console logging, update the client disconnect handler to use the logger and the connection manager pattern.

     // Handle client disconnect
     req.on('close', () => {
-      console.log(`Client disconnected for session ${sessionId}`);
-      if (activeTransport === transport && activeSessionId === sessionId) {
-        console.log('Cleaning up transport for disconnected session');
-        activeTransport = null;
-        activeSessionId = null;
-      }
-      transport.close();
+      const logger = req.scope.resolve('logger');
+      logger.info(`Client disconnected for session ${sessionId}`);
+      connectionManager.closeConnection(sessionId);
     });
📝 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.

    req.on('close', () => {
      const logger = req.scope.resolve('logger');
      logger.info(`Client disconnected for session ${sessionId}`);
      connectionManager.closeConnection(sessionId);
    });
apps/medusa/src/api/mcp/tests/__tests__/categories/fetch-categories.test.ts (3)

204-207: 🛠️ Refactor suggestion

Replace debug console.log with test assertion.

Convert this debug logging to a proper test assertion or remove it entirely.

-      console.log('Hierarchy traversal:', {
-        depth,
-        path: traversalPath,
-      });
+      // Verify the path has the expected entries
+      expect(traversalPath.length).toBeGreaterThanOrEqual(5);
📝 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.

      // Verify the path has the expected entries
      expect(traversalPath.length).toBeGreaterThanOrEqual(5);

80-80: 💡 Verification agent

🧩 Analysis chain

Unskip or explain skipped test.

This test for fetching category hierarchy is currently skipped. Either implement and unskip the test or add a comment explaining why it's skipped and when it will be addressed.


🏁 Script executed:

#!/bin/bash
# Check if there are any other skipped tests in the codebase
rg "it\.skip|test\.skip|describe\.skip" --type ts

Length of output: 758


Action Required: Address Skipped Test for Category Hierarchy

The test at apps/medusa/src/api/mcp/tests/__tests__/categories/fetch-categories.test.ts on line 80 is currently marked as skipped. Please either:

  • Remove the .skip if the test is ready to be executed, or
  • Add an inline comment explaining why this test is skipped and indicate when or under what conditions it will be enabled.

Also, note that similar skip notations appear in other test files. Ensure that any intentional skips across the suite are consistently documented.


151-151: 🛠️ Refactor suggestion

Unskip or explain skipped performance test.

The performance test for handling deep hierarchies is currently skipped. Either implement and unskip the test or add a comment explaining why it's skipped and when it will be addressed.

-  it.skip('should handle deep hierarchies efficiently', async () => {
+  // TODO: Enable this test once the deep hierarchy fetching is optimized (JIRA-1234)
+  it.skip('should handle deep hierarchies efficiently', async () => {
📝 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.

    // TODO: Enable this test once the deep hierarchy fetching is optimized (JIRA-1234)
    it.skip('should handle deep hierarchies efficiently', async () => {
apps/medusa/src/api/mcp/servers/medusa/tools/products/delete-products.ts (1)

14-47: 💡 Verification agent

🧩 Analysis chain

Consider confirming deletion for products with variants

The current implementation deletes products without checking if they have variants. Consider adding a warning or confirmation step when deleting products with variants to prevent accidental data loss.


🏁 Script executed:

#!/bin/bash
# Check if there's any usage of confirmation steps for deletion in the codebase

echo "Searching for confirmation patterns in deletion functions..."
rg -A 3 -B 3 "confirm.*delet|delet.*confirm" --type ts

Length of output: 187


Action Required: Add Confirmation for Deletion of Products with Variants

In the deletion flow in apps/medusa/src/api/mcp/servers/medusa/tools/products/delete-products.ts (lines 14-47), no confirmation step is currently implemented—even when products having associated variants are being deleted. This could lead to accidental data loss. Please consider implementing a confirmation or warning mechanism when any product slated for deletion has variants. For instance:

  • After retrieving product details, check if any product’s variants array is non-empty.
  • Prompt the user (or log a warning) to explicitly confirm whether to proceed with deletion for products with variants.

This adjustment will help safeguard against unintentional deletions.

apps/medusa/src/api/mcp/servers/medusa/tools/products/update-product-variants.ts (3)

47-47: 🛠️ Refactor suggestion

Consider returning the actual updated variants from the workflow result.

Currently, you're returning the input variants in the response, not the updated variants from the workflow result. This means any changes made by the workflow aren't reflected in the response.

 return {
   content: [
     {
       type: 'text',
       text: JSON.stringify(
         {
-          variants,
+          variants: result,
           count: variants.length,
           message: `Successfully updated ${variants.length} variants`,
         },
         null,
         2,
       ),
     },
   ],
 };
📝 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.

 return {
   content: [
     {
       type: 'text',
       text: JSON.stringify(
         {
           variants: result,
           count: variants.length,
           message: `Successfully updated ${variants.length} variants`,
         },
         null,
         2,
       ),
     },
   ],
 };

39-39: 🛠️ Refactor suggestion

Verify that result is actually returned from the workflow.

The code doesn't check if result exists before proceeding, unlike the inventory level tools which have this check. This could lead to issues if the workflow doesn't return a result.

 const { result } = await updateProductVariantsWorkflow(req.scope).run({
   input: {
     product_variants: variants,
   },
 });

+if (!result) {
+  throw new Error('Failed to update product variants');
+}
+
 logger.info(`Successfully updated ${variants.length} variants`);
📝 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.

 const { result } = await updateProductVariantsWorkflow(req.scope).run({
   input: {
     product_variants: variants,
   },
 });

 if (!result) {
   throw new Error('Failed to update product variants');
 }

 logger.info(`Successfully updated ${variants.length} variants`);

33-37: 💡 Verification agent

🧩 Analysis chain

Fix type mismatch in workflow input.

There's a TypeScript error in the pipeline that indicates the variants type doesn't match the expected UpsertProductVariantDTO type required by the workflow.


🏁 Script executed:

#!/bin/bash
# Check the expected type definition for UpsertProductVariantDTO

# First, find where UpsertProductVariantDTO is defined
fd "UpsertProductVariantDTO" --type file | xargs rg "export (type|interface) UpsertProductVariantDTO" -A 15

# Then look at how it's used in the workflow
rg "updateProductVariantsWorkflow" --type ts

Length of output: 1990


Resolve Type Mismatch in Workflow Input

The call to updateProductVariantsWorkflow is still causing a TypeScript error because the payload provided in the input field doesn’t conform to the UpsertProductVariantDTO definition (see apps/medusa/src/api/mcp/servers/medusa/tools/products/product.types.ts). Please verify and update the structure of the variants variable so that each variant fully complies with the expected type (for example, ensuring any required fields or optional properties like id are properly handled).

  • Action Points:
    • Check that each object in variants matches the UpsertProductVariantDTO interface, which extends UpdateProductVariantDTO.
    • Modify or map the variants payload as needed to include all expected properties.
    • Re-run TypeScript checks to ensure the error is resolved.

Diff context for reference:

      const { result } = await updateProductVariantsWorkflow(req.scope).run({
-        input: {
-          product_variants: variants,
-        },
-      });
+        input: {
+          product_variants: /* Ensure each variant object here adheres to UpsertProductVariantDTO (e.g., include optional 'id' property if required) */,
+        },
+      });

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 GitHub Actions: Training Demo Deploy

[error] 35-35: TS2322: Type '{ id: string; allow_backorder: boolean; manage_inventory: boolean; options?: Record<string, string> | undefined; length?: number | undefined; metadata?: Record<string, unknown> | null | undefined; ... 13 more ...; prices?: { ...; }[] | undefined; }[]' is not assignable to type '(UpsertProductVariantDTO & { prices?: Partial[] | undefined; })[]'.

apps/medusa/src/api/mcp/tests/__tests__/categories/update-categories.test.ts (1)

296-319: 💡 Verification agent

🧩 Analysis chain

Consider implementing a test for circular parent references.

The skipped test indicates that the MCP server currently allows circular parent references, which could lead to infinite loops in category hierarchies. While this is marked as an edge case, it would be valuable to implement this test and update the server to handle circular references appropriately.


🏁 Script executed:

#!/bin/bash
# Check if there are other skipped tests with similar patterns
rg "it\.skip|test\.skip" apps/medusa/src/api/mcp/tests

Length of output: 605


Action Required: Implement and validate circular reference handling

The test for circular parent references is still skipped, indicating that the MCP server currently accepts circular relationships, which may lead to infinite loops when processing category hierarchies. Please implement this test to ensure that when a category is updated to reference itself (or create a cycle), the server returns an error (matching /circular.*reference/i). This will also require updating the server logic to properly detect and reject circular references.

  • File: apps/medusa/src/api/mcp/tests/__tests__/categories/update-categories.test.ts (lines 296-319)
  • Context: The same pattern of skipped tests appears in other MCP test files, emphasizing edge cases that need to be formally validated.
apps/medusa/src/api/mcp/servers/medusa/tools/shipping/update-shipping-profiles.ts (1)

14-14: 💡 Verification agent

🧩 Analysis chain

Verify that the type enum captures all possible shipping profile types.

The type field is constrained to 'default' or 'custom'. Ensure these are the only valid types in the system, or expand the enum to include all possible values.


🏁 Script executed:

#!/bin/bash
# Search for other occurrences of shipping profile types in the codebase
rg "type.*(?:default|custom)" --type ts apps/medusa/src

Length of output: 3319


Action Required: Revisit the Shipping Profile Type Enum

The current enum in apps/medusa/src/api/mcp/servers/medusa/tools/shipping/update-shipping-profiles.ts only includes 'default' and 'custom'. However, our tests (see apps/medusa/src/api/mcp/tests/__tests__/shipping/helpers.ts) define shipping profile types as 'default' | 'gift_card' | 'custom'. This indicates that 'gift_card' is an expected valid type.

  • File to update: apps/medusa/src/api/mcp/servers/medusa/tools/shipping/update-shipping-profiles.ts (line 14)
  • Suggestion: If 'gift_card' is a valid shipping profile type for the system, update the enum accordingly (e.g., z.enum(['default', 'gift_card', 'custom'])). Otherwise, if only 'default' and 'custom' should be allowed, adjust the tests/other code accordingly.
apps/medusa/src/api/mcp/servers/medusa/tools/products/create-product-variants.ts (1)

70-75: ⚠️ Potential issue

Address the type mismatch in variant creation.

The pipeline indicates that inventory_quantity and manage_inventory are not recognized properties in the expected CreateProductVariantDTO. You can move these values into the inventory_items array or other recognized fields. For instance:

- product_variants: transformedVariants.map((variant) => ({
-   ...variant,
-   product_id,
- })),

+ product_variants: transformedVariants.map(({ inventory_quantity, manage_inventory, ...rest }) => ({
+   ...rest,
+   product_id,
+   // Example: Use manage_inventory only if recognized by the workflow
+   ...(manage_inventory ? { manage_inventory } : {}),
+   // If inventory_quantity should map to inventory_items
+   ...(inventory_quantity !== undefined
+     ? {
+         inventory_items: [
+           {
+             inventory_item_id: "some-item-id",
+             required_quantity: inventory_quantity
+           }
+         ]
+       }
+     : {})
+ })),

Confirm the needed structure in your workflow or definitions to avoid the TS2322 error.

📝 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.

          product_variants: transformedVariants.map(({ inventory_quantity, manage_inventory, ...rest }) => ({
            ...rest,
            product_id,
            // Example: Use manage_inventory only if recognized by the workflow
            ...(manage_inventory ? { manage_inventory } : {}),
            // If inventory_quantity should map to inventory_items
            ...(inventory_quantity !== undefined
              ? {
                  inventory_items: [
                    {
                      inventory_item_id: "some-item-id",
                      required_quantity: inventory_quantity
                    }
                  ]
                }
              : {})
          })),
        },
      });
🧰 Tools
🪛 GitHub Actions: Training Demo Deploy

[error] 70-70: TS2322: Type '{ product_id: string; manage_inventory: boolean; inventory_quantity?: number | undefined; sku: string | undefined; barcode: string | undefined; ean: string | undefined; upc: string | undefined; ... 14 more ...; prices?: { ...; }[] | undefined; }[]' is not assignable to type '(CreateProductVariantDTO & { prices?: CreateMoneyAmountDTO[] | undefined; } & { inventory_items?: { inventory_item_id: string; required_quantity?: number | undefined; }[] | undefined; })[]'.

apps/medusa/src/api/mcp/tests/__tests__/products/create-products.test.ts (1)

47-62: 🛠️ Refactor suggestion

Extract the shipping profile retrieval into a helper function.

This pattern of fetching the default shipping profile is repeated in multiple test cases (lines 47-62, 99-114, 168-182). Creating a reusable helper would reduce duplication.

+ async function fetchDefaultShippingProfile(client: any) {
+   const fetchShippingProfileResult = (await client.callTool({
+     name: 'fetch-shipping-profiles',
+     arguments: {
+       type: 'default',
+       limit: 1,
+     },
+   })) as Result;
+ 
+   const shippingProfileContent = JSON.parse(
+     (fetchShippingProfileResult as Result & { content: Array<{ text: string }> }).content[0].text,
+   );
+   if (!shippingProfileContent.shipping_profiles?.length) {
+     throw new Error('No default shipping profile found. Please ensure seed data is loaded.');
+   }
+   return shippingProfileContent.shipping_profiles[0].id;
+ }

  it('should successfully create a single product', async () => {
    const client = getClient();

    // Fetch default shipping profile
-   const fetchShippingProfileResult = (await client.callTool({
-     name: 'fetch-shipping-profiles',
-     arguments: {
-       type: 'default',
-       limit: 1,
-     },
-   })) as Result;
-
-   const shippingProfileContent = JSON.parse(
-     (fetchShippingProfileResult as Result & { content: Array<{ text: string }> }).content[0].text,
-   );
-   if (!shippingProfileContent.shipping_profiles?.length) {
-     throw new Error('No default shipping profile found. Please ensure seed data is loaded.');
-   }
-   const shippingProfileId = shippingProfileContent.shipping_profiles[0].id;
+   const shippingProfileId = await fetchDefaultShippingProfile(client);
📝 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.

async function fetchDefaultShippingProfile(client: any) {
  const fetchShippingProfileResult = (await client.callTool({
    name: 'fetch-shipping-profiles',
    arguments: {
      type: 'default',
      limit: 1,
    },
  })) as Result;

  const shippingProfileContent = JSON.parse(
    (fetchShippingProfileResult as Result & { content: Array<{ text: string }> }).content[0].text,
  );
  if (!shippingProfileContent.shipping_profiles?.length) {
    throw new Error('No default shipping profile found. Please ensure seed data is loaded.');
  }
  return shippingProfileContent.shipping_profiles[0].id;
}

it('should successfully create a single product', async () => {
  const client = getClient();

  // Fetch default shipping profile
  const shippingProfileId = await fetchDefaultShippingProfile(client);

  // ... rest of the test code
});
apps/medusa/src/api/mcp/tests/__tests__/products/TODO (not working yet)/update-multiple-products.todo.ts (3)

1-41: 💡 Verification agent

🧩 Analysis chain

Remove or improve the TODO indication in folder structure.

The file is in a folder named "TODO (not working yet)" which suggests it's incomplete or not functional. When committing code, it's better to either:

  1. Move working tests to the appropriate folder without "TODO"
  2. Add explicit TODOs in the code with assignees

Also, consider consolidating the response interfaces for better reusability.


🏁 Script executed:

#!/bin/bash
# Check how many test files are in this TODO directory structure
find apps/medusa/src/api/mcp/tests/__tests__/products/TODO* -type f | wc -l

Length of output: 98


Action: Address test folder naming and interface consolidation

  • The test file in apps/medusa/src/api/mcp/tests/__tests__/products/TODO (not working yet) is the sole file in that folder, which implies it might either be incomplete or pending further work.
  • If the test is complete and intended to be run, please move it to its proper location (i.e., remove the “TODO” prefix from the folder name).
  • Otherwise, if it’s genuinely under development, add explicit TODO comments within the file (with assignees) to indicate what work remains.
  • Additionally, consider consolidating the various response interfaces (e.g., ProductsResponse, ErrorResponse) into a centralized module to enhance reusability and consistency across tests.

142-149: 🛠️ Refactor suggestion

Remove debug console logs before merging to production.

There are numerous console.log statements throughout the test file that should be removed or replaced with proper test logging.

-      console.log('Updating product with data:', updateData);
+      // Use proper test logging if needed
       const result = (await client.callTool({
         name: 'update-products',
         arguments: updateData,
       })) as TypedResult;

-      console.log('Update response:', result.content[0].text);
+      // Use proper test logging if needed
📝 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.

      // Use proper test logging if needed
      const result = (await client.callTool({
        name: 'update-products',
        arguments: updateData,
      })) as TypedResult;
      // Use proper test logging if needed

154-158: 🛠️ Refactor suggestion

Extract error response handling to a helper function.

The same error handling logic is repeated in multiple test cases. Extract it to a reusable function to reduce duplication.

+ function handleErrorResponse(response: ProductsResponse | ErrorResponse, context: string): ProductsResponse {
+   if ('error' in response) {
+     console.error(`Error ${context}:`, response);
+     throw new Error(`Failed to ${context}: ${response.message}`);
+   }
+   return response as ProductsResponse;
+ }

  // Handle error response
-  if ('error' in response) {
-    console.error('Error updating product:', response);
-    throw new Error(`Failed to update product: ${response.message}`);
-  }
+  handleErrorResponse(response, 'update product');
📝 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.

+ function handleErrorResponse(response: ProductsResponse | ErrorResponse, context: string): ProductsResponse {
+   if ('error' in response) {
+     console.error(`Error ${context}:`, response);
+     throw new Error(`Failed to ${context}: ${response.message}`);
+   }
+   return response as ProductsResponse;
+ }
 
      // Handle error response
-  if ('error' in response) {
-    console.error('Error updating product:', response);
-    throw new Error(`Failed to update product: ${response.message}`);
-  }
+  handleErrorResponse(response, 'update product');
apps/medusa/src/api/mcp/servers/medusa/tools/products/delete-product-variants.ts (2)

30-56: 🛠️ Refactor suggestion

Simplify error handling and use result from workflow.

The nested try-catch structure makes the code harder to follow. Also, the current implementation doesn't use the actual deletion result from the workflow, which could lead to inconsistencies if some variants weren't deleted.

-      try {
-        const { result } = await deleteProductVariantsWorkflow(req.scope).run({
-          input: {
-            ids: variant_ids,
-          },
-        });
-
-        logger.info(`Successfully deleted ${variant_ids.length} variants`);
-
-        return {
-          content: [
-            {
-              type: 'text',
-              text: JSON.stringify(
-                {
-                  deleted_variant_ids: variant_ids,
-                  message: `Successfully deleted ${variant_ids.length} variants`,
-                },
-                null,
-                2,
-              ),
-            },
-          ],
-        };
-      } catch (error) {
-        throw new ToolExecutionError(`Failed to delete variants: ${(error as Error).message || 'Unknown error'}`);
-      }
+      const { result } = await deleteProductVariantsWorkflow(req.scope).run({
+        input: {
+          ids: variant_ids,
+        },
+      }).catch(error => {
+        throw new ToolExecutionError(`Failed to delete variants: ${(error as Error).message || 'Unknown error'}`);
+      });
+
+      const deletedIds = result?.deleted_variant_ids || variant_ids;
+      logger.info(`Successfully deleted ${deletedIds.length} variants`);
+
+      return {
+        content: [
+          {
+            type: 'text',
+            text: JSON.stringify(
+              {
+                deleted_variant_ids: deletedIds,
+                message: `Successfully deleted ${deletedIds.length} variants`,
+              },
+              null,
+              2,
+            ),
+          },
+        ],
+      };
📝 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.

      const { result } = await deleteProductVariantsWorkflow(req.scope).run({
        input: {
          ids: variant_ids,
        },
      }).catch(error => {
        throw new ToolExecutionError(`Failed to delete variants: ${(error as Error).message || 'Unknown error'}`);
      });

      const deletedIds = result?.deleted_variant_ids || variant_ids;
      logger.info(`Successfully deleted ${deletedIds.length} variants`);

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(
              {
                deleted_variant_ids: deletedIds,
                message: `Successfully deleted ${deletedIds.length} variants`,
              },
              null,
              2,
            ),
          },
        ],
      };

25-28: 🛠️ Refactor suggestion

Remove hardcoded test-specific logic from production code.

The special case for a non-existent variant ID should be handled in test mocks rather than in the production code. This enhances maintainability and keeps production code clean.

-      // Special case for test - non-existent variant ID
-      if (variant_ids.includes('non-existent-id')) {
-        throw new Error('Variant with ID non-existent-id does not exist');
-      }
📝 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.


apps/medusa/src/api/mcp/servers/medusa/tools/products/fetch-products.ts (1)

66-66: ⚠️ Potential issue

Remove or address the unused @ts-expect-error directive.

This directive is causing a pipeline failure (TS2578). If the error is truly resolved, removing it avoids false positives.

-        // @ts-expect-error - The filter types are not properly defined
📝 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.

// (The unused @ts-expect-error directive has been removed)
🧰 Tools
🪛 GitHub Actions: Training Demo Deploy

[error] 66-66: TS2578: Unused '@ts-expect-error' directive.

apps/medusa/src/api/mcp/servers/medusa/tools/products/fetch-product-variants.ts (2)

120-120: ⚠️ Potential issue

Remove or fix the unused @ts-expect-error directive.

Similarly, the pipeline complains about this unused directive. Remove or validate it as needed.

-        // @ts-expect-error
📝 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.


🧰 Tools
🪛 GitHub Actions: Training Demo Deploy

[error] 120-120: TS2578: Unused '@ts-expect-error' directive.


65-65: ⚠️ Potential issue

Remove or fix the unused @ts-expect-error directive.

TS2578 flags this as unused. Consider removing the directive or providing a valid justification for it.

-        // @ts-expect-error
📝 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.


🧰 Tools
🪛 GitHub Actions: Training Demo Deploy

[error] 65-65: TS2578: Unused '@ts-expect-error' directive.

apps/medusa/src/api/chat/route.ts (2)

140-206: 🛠️ Refactor suggestion

Handle non-tool-related errors more gracefully in experimental_repairToolCall.
The repair logic is robust for mismatched or missing tool arguments, but broad catch blocks might inadvertently swallow non-tool-related errors. Consider stricter error type checks or rethrow for errors that are clearly not about invalid tool arguments.


253-281: 🛠️ Refactor suggestion

Use a Set-like Redis structure for the chat index.
Currently, you parse JSON, push to an array, and overwrite the key. This approach can lead to synchronization issues if two processes add different chat IDs at the same time. Using a Redis set (e.g., SADD) or a transactional approach can help avoid duplicates and race conditions.

apps/medusa/src/api/mcp/servers/medusa/docs/tools/inventory/inventory-workflows.md (1)

71-99: 🛠️ Refactor suggestion

Elaborate on transaction safety for batch operations.
The doc references batch workflows for inventory items. In a production scenario, partial failures or concurrency conflicts could occur. Add a note about how to handle or retry partial failures to maintain data integrity.

apps/medusa/src/api/mcp/servers/medusa/tools/stock-locations/TODO (not working yet)/upsert-stock-location-addresses.ts (2)

6-16: ⚠️ Potential issue

Possible confusion with input structure vs. usage.
Although this interface defines typical address fields, the code later references location_id and item.address, which are not reflected here. Ensure that either this interface or the usage code is updated to match the actual incoming data structure.


74-90: ⚠️ Potential issue

Mismatch between interface and usage.
The code references item.location_id and item.address, but the UpsertStockLocationAddressInput interface only has address fields (lines 7-16). This will likely cause runtime or type-check issues.

A possible fix is to define a higher-level interface that includes location_id and a nested address object. For instance:

-interface UpsertStockLocationAddressInput {
-  ...
-}
+interface LocationAddressPayload {
+  location_id: string;
+  address: UpsertStockLocationAddressInput;
+}

Committable suggestion skipped: line range outside the PR's diff.

apps/medusa/src/admin/widgets/product-details-product-reviews.tsx (1)

6-12: 🛠️ Refactor suggestion

Correct component naming inconsistency

The component is named OrderDetailsProductReviewsWidget but it's used in the product details context, not order details. This is inconsistent with the file name, props, and zone configuration.

-const OrderDetailsProductReviewsWidget = ({ data: product }: DetailWidgetProps<AdminProduct>) => {
+const ProductDetailsProductReviewsWidget = ({ data: product }: DetailWidgetProps<AdminProduct>) => {

Also update the default export on line 19 to match the new name.

📝 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.

const ProductDetailsProductReviewsWidget = ({ data: product }: DetailWidgetProps<AdminProduct>) => {
  return (
    <Container className="mb-2">
      <ProductReviewDataTable defaultQuery={{ product_id: product.id }} />
    </Container>
  );
};

export default ProductDetailsProductReviewsWidget;
apps/medusa/src/api/admin/product-reviews/route.ts (1)

5-20: 🛠️ Refactor suggestion

Add error handling to the API endpoint

The GET handler lacks error handling. If the remoteQuery execution fails or encounters issues, there's no mechanism to gracefully handle the error and provide a meaningful response to the client.

Consider adding a try/catch block:

 export const GET = async (req: AuthenticatedMedusaRequest, res: MedusaResponse) => {
   const remoteQuery = req.scope.resolve('remoteQuery');

   const queryObject = remoteQueryObjectFromString({
     entryPoint: 'product_review',
     variables: {
       filters: req.filterableFields,
       ...req.queryConfig.pagination,
     },
     fields: req.queryConfig.fields as RemoteQueryObjectConfig<'product_review'>['fields'],
   });

+  try {
     const { rows: product_reviews, metadata } = await remoteQuery(queryObject);
     
     res.status(200).json({ product_reviews, count: metadata.count, offset: metadata.skip, limit: metadata.take });
+  } catch (error) {
+    const status = error.status || 500;
+    const message = error.message || 'An unexpected error occurred';
+    res.status(status).json({ message });
+  }
 };
📝 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.

export const GET = async (req: AuthenticatedMedusaRequest, res: MedusaResponse) => {
  const remoteQuery = req.scope.resolve('remoteQuery');

  const queryObject = remoteQueryObjectFromString({
    entryPoint: 'product_review',
    variables: {
      filters: req.filterableFields,
      ...req.queryConfig.pagination,
    },
    fields: req.queryConfig.fields as RemoteQueryObjectConfig<'product_review'>['fields'],
  });

  try {
    const { rows: product_reviews, metadata } = await remoteQuery(queryObject);
    
    res.status(200).json({ product_reviews, count: metadata.count, offset: metadata.skip, limit: metadata.take });
  } catch (error) {
    const status = error.status || 500;
    const message = error.message || 'An unexpected error occurred';
    res.status(status).json({ message });
  }
};
apps/medusa/src/api/admin/product-reviews/[id]/response/middlewares.ts (1)

10-26: 💡 Verification agent

🧩 Analysis chain

Fix TypeScript errors in middleware configuration.

The pipeline is failing due to type incompatibility between your Zod schema and the expected parameter type for validateAndTransformBody.

This is likely due to a version mismatch between Zod and the Medusa framework. Try updating the middleware implementation:

- middlewares: [validateAndTransformBody(createProductReviewResponseDTO)],
+ middlewares: [validateAndTransformBody(createProductReviewResponseDTO as any)],

Alternatively, check if there's a more type-safe solution by ensuring compatible versions between Zod and Medusa framework.


🌐 Web query:

What are the compatible versions of Zod with @medusajs/framework's validateAndTransformBody function?

💡 Result:

The compatible versions of Zod with @medusajs/framework's validateAndTransformBody function are Zod 3.22.4 or lower. This is due to dependency mismatches in Medusa's packages, where @medusajs/framework and @medusajs/medusa rely on Zod 3.22.4, while other dependencies like @medusajs/admin-sdk may use newer versions[4][7].

Attempting to use Zod versions 3.24.1 or higher results in TypeScript build errors when defining validation schemas, as seen in issues where ZodOptional types become incompatible with expected validators[4][7]. To resolve this, developers must manually install Zod 3.22.4 using:

npm uninstall zod
npm install zod@3.22.4

This version alignment ensures proper type checking and middleware functionality when using validateAndTransformBody and related validation utilities[4][7].

Citations:


Fix TypeScript middleware configuration errors

The TypeScript issues arise from a version mismatch between Zod and Medusa's validation utilities. The validateAndTransformBody function expects a schema that aligns with Zod 3.22.4 or lower. You have two options:

  • Temporary workaround: Apply an explicit cast to bypass the type incompatibility by updating the middleware as shown below:
    - middlewares: [validateAndTransformBody(createProductReviewResponseDTO)],
    + middlewares: [validateAndTransformBody(createProductReviewResponseDTO as any)],
  • Long-term fix: Align your dependencies by downgrading Zod to version 3.22.4, which is the compatible version with @medusajs/framework and its associated packages. To do so, run:
    npm uninstall zod
    npm install zod@3.22.4

Please update the configuration accordingly to ensure the middleware functions correctly without TypeScript errors.

🧰 Tools
🪛 GitHub Actions: Training Demo Deploy

[error] 14-14: TS2345: Argument of type 'ZodObject<{ content: ZodString; }, "strip", ZodTypeAny, { content: string; }, { content: string; }>' is not assignable to parameter of type 'ZodObject<any, any, ZodTypeAny, { [x: string]: any; }, { [x: string]: any; }>'. Types have separate declarations of a private property '_cached'.


[error] 19-19: TS2345: Argument of type 'ZodObject<{ content: ZodString; }, "strip", ZodTypeAny, { content: string; }, { content: string; }>' is not assignable to parameter of type 'ZodObject<any, any, ZodTypeAny, { [x: string]: any; }, { [x: string]: any; }>'. Types have separate declarations of a private property '_cached'.

apps/medusa/src/admin/components/molecules/ProductReviewDataTable.tsx (1)

126-127: 🛠️ Refactor suggestion

Remove TypeScript ignore comment.

The @ts-ignore comment suggests a type definition issue that should be addressed properly rather than ignored.

Try to fix the underlying type issue instead of ignoring it:

- // @ts-ignore
- id: 'actions',
+ id: 'actions' as const,

Or check if there's a more recent version of the DataTable component that properly types action columns.

📝 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.

      id: 'actions' as const,
apps/medusa/src/api/admin/product-reviews/middlewares.ts (1)

5-18: ⚠️ Potential issue

Fix TypeScript errors in query schema definition

The build pipeline is failing due to type incompatibilities between Zod and the Medusa framework. There are two specific TypeScript errors:

  1. TS2345: Argument of type 'ZodObject<...>' is not assignable to parameter of type 'AnyZodObject'
  2. TS2739: Type 'ZodOptional<ZodUnion<...>>' is missing properties from type 'ZodType<any, any, any>'

These type errors need to be resolved by ensuring compatibility between the Zod version and the Medusa framework. Consider updating the merge implementation:

-}).merge(
-  z.object({
+}), z.object({

If that doesn't work, you may need to use a different pattern for combining schemas or update the dependency versions.

📝 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.

export const listAdminProductReviewsQuerySchema = createFindParams({
  offset: 0,
  limit: 50,
}, z.object({
  q: z.string().optional(),
  id: z.union([z.string(), z.array(z.string())]).optional(),
  product_id: z.union([z.string(), z.array(z.string())]).optional(),
  order_id: z.union([z.string(), z.array(z.string())]).optional(),
  rating: z.union([z.number().max(5).min(1), z.array(z.number().max(5).min(1))]).optional(),
  created_at: createOperatorMap().optional(),
  updated_at: createOperatorMap().optional(),
}),
);
🧰 Tools
🪛 GitHub Actions: Training Demo Deploy

[error] 9-9: TS2345: Argument of type 'ZodObject<ZodRawShape, "strip", ZodTypeAny, { [x: string]: any; }, { [x: string]: any; }>' is not assignable to parameter of type 'AnyZodObject'. Types have separate declarations of a private property '_cached'.


[error] 15-15: TS2739: Type 'ZodOptional<ZodUnion<[any, ZodObject<{ $eq: any; $ne: any; $in: any; $nin: any; $like: any; $ilike: any; $re: any; $contains: any; $gt: any; $gte: any; $lt: any; $lte: any; }, "strip", ZodTypeAny, { $eq?: any; $ne?: any; ... 9 more ...; $lte?: any; }, { ...; }>]>>' is missing the following properties from type 'ZodType<any, any, any>': "~standard", "~validate".

apps/medusa/src/admin/components/molecules/ProductReviewDetailsDrawer.tsx (3)

56-62: 🛠️ Refactor suggestion

Add key prop to mapped image elements

When rendering lists of elements in React, each element should have a unique key prop to help React identify which items have changed.

Add a unique key to each image in the map function:

  const ImagesValue = () => (
    <div className="grid grid-cols-3 gap-2">
      {review.images.map((image, index) => (
-        <img src={image.url} alt={`Review image ${index + 1}`} className="w-full h-full object-cover" />
+        <img key={image.id || index} src={image.url} alt={`Review image ${index + 1}`} className="w-full h-full object-cover" />
      ))}
    </div>
  );
📝 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.

  const ImagesValue = () => (
    <div className="grid grid-cols-3 gap-2">
      {review.images.map((image, index) => (
        <img key={image.id || index} src={image.url} alt={`Review image ${index + 1}`} className="w-full h-full object-cover" />
      ))}
    </div>
  );

32-36: 🛠️ Refactor suggestion

Add error handling for potentially missing order data

Similar to the product data, there's no check if review.order exists before accessing its properties, which could lead to runtime errors.

Add null checks for the order object:

  const OrderValue = () => (
+   review.order ? (
    <Link to={`/orders/${review.order.id}`}>
      <Text className="hover:underline">#{review.order.display_id}</Text>
    </Link>
+   ) : (
+     <Text className="text-ui-fg-subtle">No order associated</Text>
+   )
  );
📝 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.

  const OrderValue = () => (
    review.order ? (
      <Link to={`/orders/${review.order.id}`}>
        <Text className="hover:underline">#{review.order.display_id}</Text>
      </Link>
    ) : (
      <Text className="text-ui-fg-subtle">No order associated</Text>
    )
  );

19-30: 🛠️ Refactor suggestion

Add error handling for potentially missing product data

While you check if review.product.thumbnail exists, there's no check for review.product itself. This could lead to runtime errors if the product data is not available.

Add null checks for the product object:

  const ProductValue = () => (
    <div className="flex items-center gap-4">
-      {review.product.thumbnail ? (
+      {review.product?.thumbnail ? (
        <img className="h-12 w-12 flex-shrink-0 rounded-md" src={review.product.thumbnail} alt={review.product.title} />
      ) : (
        <div className="h-12 w-12 flex-shrink-0 rounded-md bg-gray-200" />
      )}
-      <Link to={`/products/${review.product.id}`}>
-        <Text className="hover:underline">{review.product.title}</Text>
+      <Link to={`/products/${review.product?.id || ''}`}>
+        <Text className="hover:underline">{review.product?.title || 'Unknown Product'}</Text>
      </Link>
    </div>
  );
📝 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.

  const ProductValue = () => (
    <div className="flex items-center gap-4">
      {review.product?.thumbnail ? (
        <img className="h-12 w-12 flex-shrink-0 rounded-md" src={review.product.thumbnail} alt={review.product.title} />
      ) : (
        <div className="h-12 w-12 flex-shrink-0 rounded-md bg-gray-200" />
      )}
      <Link to={`/products/${review.product?.id || ''}`}>
        <Text className="hover:underline">{review.product?.title || 'Unknown Product'}</Text>
      </Link>
    </div>
  );
apps/medusa/src/api/admin/product-review-stats/middlewares.ts (1)

5-16: ⚠️ Potential issue

Fix TypeScript errors in query schema definition

The build pipeline is failing with the same TypeScript errors as in the product-reviews middleware file:

  1. TS2345: Argument of type 'ZodObject<...>' is not assignable to parameter of type 'AnyZodObject'
  2. TS2739: Type 'ZodOptional<ZodUnion<...>>' is missing properties from type 'ZodType<any, any, any>'

These type errors need to be resolved by ensuring compatibility between the Zod version and the Medusa framework. Apply the same fix as suggested for the product-reviews middleware:

-}).merge(
-  z.object({
+}), z.object({

If that doesn't work, you may need to use a different pattern for combining schemas or update the dependency versions.

📝 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.

export const listAdminProductReviewStatsQuerySchema = createFindParams(
  {
    offset: 0,
    limit: 50,
  },
  z.object({
    id: z.union([z.string(), z.array(z.string())]).optional(),
    product_id: z.union([z.string(), z.array(z.string())]).optional(),
    average_rating: z.union([
      z.number().max(5).min(1),
      z.array(z.number().max(5).min(1))
    ]).optional(),
    created_at: createOperatorMap().optional(),
    updated_at: createOperatorMap().optional(),
  })
);
🧰 Tools
🪛 GitHub Actions: Training Demo Deploy

[error] 9-9: TS2345: Argument of type 'ZodObject<ZodRawShape, "strip", ZodTypeAny, { [x: string]: any; }, { [x: string]: any; }>' is not assignable to parameter of type 'AnyZodObject'. Types have separate declarations of a private property '_cached'.


[error] 13-13: TS2739: Type 'ZodOptional<ZodUnion<[any, ZodObject<{ $eq: any; $ne: any; $in: any; $nin: any; $like: any; $ilike: any; $re: any; $contains: any; $gt: any; $gte: any; $lt: any; $lte: any; }, "strip", ZodTypeAny, { $eq?: any; $ne?: any; ... 9 more ...; $lte?: any; }, { ...; }>]>>' is missing the following properties from type 'ZodType<any, any, any>': "~standard", "~validate".

apps/medusa/src/api/admin/product-reviews/[id]/response/route.ts (1)

4-4: ⚠️ Potential issue

Fix the broken import path.
The pipeline error indicates that '../../../../../../.medusa/types/query-entry-points' may not exist or lacks type definitions, blocking compilation. Confirm the correct path or ensure the file is generated.

- import { ProductReview, ProductReviewResponse } from '../../../../../../.medusa/types/query-entry-points';
+ // Update this import with the correct location of the type declarations
📝 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.

// Update this import with the correct location of the type declarations
🧰 Tools
🪛 GitHub Actions: Training Demo Deploy

[error] 4-4: TS2307: Cannot find module '../../../../../../.medusa/types/query-entry-points' or its corresponding type declarations.

apps/medusa/src/admin/utils/is-mac-keyboard.ts (1)

1-10: 💡 Verification agent

🧩 Analysis chain

Be cautious with navigator.platform usage

The function correctly handles SSR by checking for navigator availability, but navigator.platform is deprecated and might not be supported in future browser versions.


🌐 Web query:

Is navigator.platform deprecated? What's the recommended alternative?

💡 Result:

The navigator.platform property is not officially deprecated in web specifications, though its use is generally discouraged due to privacy and fingerprinting concerns. MDN Web Docs initially marked it as deprecated in error but later revised their documentation to clarify this[1][4].

Key Details:

  • Deprecation Status:
    The property remains part of the HTML standard but is considered unreliable for feature detection. MDN removed the deprecation notice after confirming no spec-level deprecation exists[1][4].

  • Recommended Alternatives:

    1. Feature Detection:
      Prefer detecting specific browser capabilities (e.g., 'serviceWorker' in navigator) rather than platform checks[1][6].
    2. User-Agent Client Hints API:
      For Chromium-based browsers, navigator.userAgentData.platform provides similar functionality but is not yet widely adopted[1][6]. Use a fallback for compatibility:
      const platform = navigator.userAgentData?.platform || navigator.platform || 'unknown';
  • Why Avoid navigator.platform?

    • Privacy risks: Platform detection can contribute to user fingerprinting[6][10].
    • Inconsistent values: Returns strings like "MacIntel" or "Win32", which may not reliably indicate OS capabilities[1][7].

Summary:

While navigator.platform isn’t deprecated, modern best practices favor feature detection or privacy-conscious APIs like Client Hints (where available)[1][6]. For broader compatibility, combine feature checks with fallbacks.

Citations:


Updated Platform Detection Advice in is-mac-keyboard.ts

The SSR check is correctly implemented. Note that although navigator.platform is not officially deprecated, its use is discouraged due to privacy and fingerprinting concerns—and it can return inconsistent values across browsers. To better align with modern best practices, consider adopting a hybrid approach that uses the Client Hints API where available, for example:

const platform = navigator.userAgentData?.platform || navigator.platform;
return platform.toLowerCase().includes('mac');

This change maintains compatibility while offering a more robust solution for detecting a Mac keyboard.

apps/medusa/src/admin/routes/ai-chat/components/product-list.tsx (1)

10-35: 🛠️ Refactor suggestion

Add empty state handling for zero products

While the component efficiently handles displaying products, it lacks explicit handling for when the products array is empty. Adding an empty state message would improve the user experience.

 export const ProductList = ({ products, count }: ProductListProps) => {
   return (
     <div className="w-full">
       <div className="px-4 pb-2">
         {count !== undefined && (
           <Text className="text-ui-fg-subtle text-xs">
             Found {count} product{count !== 1 ? 's' : ''}
           </Text>
         )}
       </div>

       <div className="overflow-x-auto pb-2 px-4">
+        {products.length === 0 ? (
+          <div className="flex items-center justify-center py-4">
+            <Text className="text-ui-fg-subtle">No products found</Text>
+          </div>
+        ) : (
         <div className="flex gap-4 w-max">
           {products.map((product) => (
             <div
               key={product.id}
               className="w-[280px] flex-none border border-ui-border-base rounded-lg overflow-hidden"
             >
               <ProductCard product={product} />
             </div>
           ))}
         </div>
+        )}
       </div>
     </div>
   );
 };
📝 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.

export const ProductList = ({ products, count }: ProductListProps) => {
  return (
    <div className="w-full">
      <div className="px-4 pb-2">
        {count !== undefined && (
          <Text className="text-ui-fg-subtle text-xs">
            Found {count} product{count !== 1 ? 's' : ''}
          </Text>
        )}
      </div>

      <div className="overflow-x-auto pb-2 px-4">
        {products.length === 0 ? (
          <div className="flex items-center justify-center py-4">
            <Text className="text-ui-fg-subtle">No products found</Text>
          </div>
        ) : (
          <div className="flex gap-4 w-max">
            {products.map((product) => (
              <div
                key={product.id}
                className="w-[280px] flex-none border border-ui-border-base rounded-lg overflow-hidden"
              >
                <ProductCard product={product} />
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
};
apps/medusa/src/admin/routes/ai-chat/components/ToolResults/tool-result-display.tsx (1)

10-64: 🛠️ Refactor suggestion

Add error handling for JSON.stringify

The component currently doesn't handle potential errors when using JSON.stringify with complex objects that might contain circular references or non-serializable values (like functions, Symbols, etc.).

+const safeJsonStringify = (obj: any, space?: number) => {
+  try {
+    return JSON.stringify(obj, (key, value) => 
+      typeof value === 'function' ? '[Function]' : value, space);
+  } catch (error) {
+    return `Error serializing object: ${error.message}`;
+  }
+};

export const ToolResultDisplay = ({ result }: ToolResultDisplayProps) => {
  const hasProducts = toolResultHasProducts(result);

  return (
    <div className="w-full space-y-2">
      <Drawer>
        {/* ... unchanged code ... */}
          {result.toolInvocation.args && (
              <div>
                <CodeBlock
                  title="Arguments"
                  snippets={[
-                    { code: JSON.stringify(result.toolInvocation.args, null, 2), language: 'json', label: 'Arguments' },
+                    { code: safeJsonStringify(result.toolInvocation.args, 2), language: 'json', label: 'Arguments' },
                  ]}
                >
                  <CodeBlock.Header />
                  <CodeBlock.Body />
                </CodeBlock>
              </div>
            )}
            {result.toolInvocation.state === 'result' && (
              <div>
                <CodeBlock
                  title="Results"
                  snippets={[
-                    { code: JSON.stringify(result.toolInvocation.result, null, 2), language: 'json', label: 'Results' },
+                    { code: safeJsonStringify(result.toolInvocation.result, 2), language: 'json', label: 'Results' },
                  ]}
                >
                  <CodeBlock.Header />
                  <CodeBlock.Body />
                </CodeBlock>
              </div>
            )}
📝 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.

const safeJsonStringify = (obj: any, space?: number) => {
  try {
    return JSON.stringify(obj, (key, value) =>
      typeof value === 'function' ? '[Function]' : value, space);
  } catch (error) {
    return `Error serializing object: ${error.message}`;
  }
};

export const ToolResultDisplay = ({ result }: ToolResultDisplayProps) => {
  const hasProducts = toolResultHasProducts(result);

  return (
    <div className="w-full space-y-2">
      <Drawer>
        <Drawer.Trigger asChild>
          <Button variant="secondary" size="small" className="flex items-center gap-2">
            <Code className="!bg-transparent !border-none">🛠️ {result.toolInvocation.toolName}</Code>
          </Button>
        </Drawer.Trigger>
        <Drawer.Content>
          <Drawer.Header>
            <Drawer.Title>
              <Code>🛠️ {result.toolInvocation.toolName}</Code>
            </Drawer.Title>
          </Drawer.Header>
          <Drawer.Body className="flex-1 overflow-y-auto p-4 space-y-4">
            {result.toolInvocation.args && (
              <div>
                <CodeBlock
                  title="Arguments"
                  snippets={[
                    { code: safeJsonStringify(result.toolInvocation.args, 2), language: 'json', label: 'Arguments' },
                  ]}
                >
                  <CodeBlock.Header />
                  <CodeBlock.Body />
                </CodeBlock>
              </div>
            )}
            {result.toolInvocation.state === 'result' && (
              <div>
                <CodeBlock
                  title="Results"
                  snippets={[
                    { code: safeJsonStringify(result.toolInvocation.result, 2), language: 'json', label: 'Results' },
                  ]}
                >
                  <CodeBlock.Header />
                  <CodeBlock.Body />
                </CodeBlock>
              </div>
            )}
          </Drawer.Body>
        </Drawer.Content>
      </Drawer>
      {hasProducts && result.toolInvocation.state === 'result' && (
        <div className="w-full py-4">
          <ProductList products={result.toolInvocation.result.products} count={result.toolInvocation.result.count} />
        </div>
      )}
    </div>
  );
};
apps/medusa/src/admin/hooks/use-persistent-input.ts (1)

14-44: 🛠️ Refactor suggestion

Improve useEffect dependency array

The first useEffect has setInput in its dependency array, which is likely a function from a parent component (possibly from useState). If the parent component re-renders, this could cause unnecessary execution of this effect.

  // Set initial input from local storage when component loads
  useEffect(() => {
    const storedInputValue = typeof window !== 'undefined' ? localStorage.getItem(CHAT_INPUT_VALUE_KEY) : null;

    if (storedInputValue) {
      setInput(storedInputValue);
    }
-  }, [setInput]);
+  }, []); // Empty dependency array as this should only run once on mount
📝 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.

export const usePersistentInput = (
  input: string,
  setInput: (value: string) => void,
  shouldResetInput: boolean,
): [string, (value: string) => void] => {
  // Set initial input from local storage when component loads
  useEffect(() => {
    const storedInputValue = typeof window !== 'undefined' ? localStorage.getItem(CHAT_INPUT_VALUE_KEY) : null;

    if (storedInputValue) {
      setInput(storedInputValue);
    }
  }, []); // Empty dependency array as this should only run once on mount

  // Save input to local storage when it changes
  useEffect(() => {
    if (input && typeof window !== 'undefined') {
      localStorage.setItem(CHAT_INPUT_VALUE_KEY, input);
    }
  }, [input]);

  // Clear input from local storage when shouldResetInput is true
  useEffect(() => {
    if (shouldResetInput && typeof window !== 'undefined') {
      localStorage.removeItem(CHAT_INPUT_VALUE_KEY);
    }
  }, [shouldResetInput]);

  // Return the same input and setInput functions
  return [input, setInput];
};
apps/medusa/src/admin/hooks/use-scroll-to-bottom.ts (1)

19-35: 🛠️ Refactor suggestion

Add throttling to scroll event handler

Scroll events fire frequently and can impact performance. Consider adding throttling to reduce the frequency of calculations.

import { useState, useEffect, RefObject } from 'react';

+// Add throttle utility function
+function throttle<T extends (...args: any[]) => any>(func: T, limit: number): (...args: Parameters<T>) => void {
+  let inThrottle = false;
+  return function(this: any, ...args: Parameters<T>) {
+    if (!inThrottle) {
+      inThrottle = true;
+      func.apply(this, args);
+      setTimeout(() => inThrottle = false, limit);
+    }
+  };
+}

// ... rest of code

useEffect(() => {
    const checkScroll = () => {
      if (!containerRef.current) return;

      const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
      const isAtBottom = Math.abs(scrollHeight - clientHeight - scrollTop) < threshold;
      setShowScrollButton(!isAtBottom);
    };

+   const throttledCheckScroll = throttle(checkScroll, 100);
    const container = containerRef.current;
    if (container) {
-     container.addEventListener('scroll', checkScroll);
+     container.addEventListener('scroll', throttledCheckScroll);
      checkScroll();
    }

-   return () => container?.removeEventListener('scroll', checkScroll);
+   return () => container?.removeEventListener('scroll', throttledCheckScroll);
  }, [containerRef, threshold]);
📝 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.

import { useState, useEffect, RefObject } from 'react';

// Add throttle utility function
function throttle<T extends (...args: any[]) => any>(func: T, limit: number): (...args: Parameters<T>) => void {
  let inThrottle = false;
  return function (this: any, ...args: Parameters<T>) {
    if (!inThrottle) {
      inThrottle = true;
      func.apply(this, args);
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// ... rest of code

useEffect(() => {
  const checkScroll = () => {
    if (!containerRef.current) return;

    const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
    const isAtBottom = Math.abs(scrollHeight - clientHeight - scrollTop) < threshold;
    setShowScrollButton(!isAtBottom);
  };

  const throttledCheckScroll = throttle(checkScroll, 100);
  const container = containerRef.current;
  if (container) {
    container.addEventListener('scroll', throttledCheckScroll);
    checkScroll();
  }

  return () => container?.removeEventListener('scroll', throttledCheckScroll);
}, [containerRef, threshold]);
apps/medusa/src/admin/routes/ai-chat/components/ToolResults/example-usage.tsx (3)

36-43: 🛠️ Refactor suggestion

Implement proper product deletion handler

The inline handler for product deletion only logs a message without taking any action. Consider implementing a proper handler that responds to product deletions with appropriate actions.

{
  toolName: 'mcp__delete_products',
  handler: ({ result, isStreamingMessage }) => {
    if (!isStreamingMessage || result.toolInvocation.state !== 'result') return;
    console.log('Product deletion detected!');
-   // Perform any needed actions when a product is deleted
+   // Perform actions like invalidating queries or showing notifications
+   // Example:
+   // queryClient.invalidateQueries(['products']);
+   // toast.success('Product successfully deleted');
  },
},
📝 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.

      {
        toolName: 'mcp__delete_products',
        handler: ({ result, isStreamingMessage }) => {
          if (!isStreamingMessage || result.toolInvocation.state !== 'result') return;
          console.log('Product deletion detected!');
          // Perform actions like invalidating queries or showing notifications
          // Example:
          // queryClient.invalidateQueries(['products']);
          // toast.success('Product successfully deleted');
        },
      },

92-102: 🛠️ Refactor suggestion

Add error handling for analytics API call

The current error handling for the analytics API call only logs to the console. Consider implementing proper error handling with retry logic or fallback mechanisms.

if (process.env.NODE_ENV === 'production') {
  try {
    fetch('/api/analytics/log-tool-usage', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ toolName, timestamp }),
-   });
+   }).catch(error => {
+     console.error('Network error when logging tool usage:', error);
+   });
  } catch (error) {
    console.error('Failed to log tool usage:', error);
  }
}
📝 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.

          if (process.env.NODE_ENV === 'production') {
            try {
              fetch('/api/analytics/log-tool-usage', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ toolName, timestamp }),
              }).catch(error => {
                console.error('Network error when logging tool usage:', error);
              });
            } catch (error) {
              console.error('Failed to log tool usage:', error);
            }
          }

109-111: ⚠️ Potential issue

Avoid exposing functions globally on window

Exposing functions on the global window object creates potential security risks and makes debugging more difficult. Consider using a more structured approach like a context provider or a dedicated service.

- // Expose the functions globally if needed
- (window as any).__processToolResult = processToolResult;
- (window as any).__resetToolResultTracking = resetProcessedResults;
+ // If global access is needed, consider using a dedicated service or context

Committable suggestion skipped: line range outside the PR's diff.

apps/medusa/src/admin/routes/ai-chat/components/ChatPortal/chat-portal.tsx (1)

46-55: 🛠️ Refactor suggestion

Move nested component outside main component

Defining the ChatContentComponent within the render method can cause unnecessary re-renders. Move this component outside the main component or memoize it to prevent performance issues.

- const ChatContentComponent = () => (
-   <ChatContent
-     onClose={() => setStatus('closed')}
-     onExpand={handleViewToggle}
-     onNewChat={startNewChat}
-     onDirectionToggle={handleDirectionToggle}
-     status={status}
-     drawerDirection={drawerDirection}
-   />
- );

export const ChatPortal = () => {
  // ... existing code

And then at the bottom of the file:

+ const ChatContentComponent = ({
+   setStatus,
+   handleViewToggle,
+   startNewChat,
+   handleDirectionToggle,
+   status,
+   drawerDirection,
+ }) => (
+   <ChatContent
+     onClose={() => setStatus('closed')}
+     onExpand={handleViewToggle}
+     onNewChat={startNewChat}
+     onDirectionToggle={handleDirectionToggle}
+     status={status}
+     drawerDirection={drawerDirection}
+   />
+ );
📝 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.

export const ChatPortal = () => {
  // ... existing code and state definitions

  return (
    <div>
      {/* Other components and markup */}
      <ChatContentComponent 
        setStatus={setStatus}
        handleViewToggle={handleViewToggle}
        startNewChat={startNewChat}
        handleDirectionToggle={handleDirectionToggle}
        status={status}
        drawerDirection={drawerDirection}
      />
    </div>
  );
};

// Moved outside of ChatPortal to prevent unnecessary re-renders
const ChatContentComponent = ({
  setStatus,
  handleViewToggle,
  startNewChat,
  handleDirectionToggle,
  status,
  drawerDirection,
}) => (
  <ChatContent
    onClose={() => setStatus('closed')}
    onExpand={handleViewToggle}
    onNewChat={startNewChat}
    onDirectionToggle={handleDirectionToggle}
    status={status}
    drawerDirection={drawerDirection}
  />
);
apps/medusa/src/admin/routes/ai-chat/components/ToolResults/use-tool-handlers.ts (2)

17-38: 🛠️ Refactor suggestion

Potential issue with hot module replacement

Using a module-level variable (defaultHandlersRegistered) to track registration state can cause issues with hot module replacement. Consider moving this state inside the hook or using a more reliable tracking mechanism.

- let defaultHandlersRegistered = false;
- function registerDefaultHandlers() {
-   if (defaultHandlersRegistered) return;
+ function registerDefaultHandlers(alreadyRegistered: boolean) {
+   if (alreadyRegistered) return;
  
  // Register product handlers
  registerProductUpdateHandler(invalidateProductQueriesHandler);
  registerProductCreateHandler(invalidateProductQueriesHandler);
  registerProductUpdateHandler(productToastNotificationHandler);
  registerProductCreateHandler(productToastNotificationHandler);

  // Register error notification handler for all tools
  toolHandlerRegistry.registerWildcardHandler(errorNotificationHandler);

  // Register inventory handlers
  registerInventoryHandler(invalidateInventoryQueriesHandler);

  // Register logging for all tools (wildcard)
  toolHandlerRegistry.registerWildcardHandler(logToolInvocationHandler);

-   defaultHandlersRegistered = true;
+   return true;
}

Then update the usage in the hook:

+ // Track if default handlers have been registered
+ const defaultHandlersRegisteredRef = useRef(false);

// Register handlers
useEffect(() => {
  // Register built-in handlers if requested
  if (shouldRegisterDefaults) {
-     registerDefaultHandlers();
+     defaultHandlersRegisteredRef.current = registerDefaultHandlers(defaultHandlersRegisteredRef.current);
  }
  
  // ...rest of the code
}

Committable suggestion skipped: line range outside the PR's diff.


118-146: 🛠️ Refactor suggestion

Add try/catch block for handling errors during processing

The processToolResult function doesn't handle errors that might occur during handler execution. Add a try/catch block to prevent failures from crashing the application.

const processToolResult = useCallback(
  async (result: ToolPart, message: Message, isStreamingMessage: boolean) => {
    // Generate a unique key for this tool result
    const resultKey = getToolResultKey(result);

    // Skip if we've already processed this exact result
    if (processedResults.current.has(resultKey)) {
      return;
    }

    // Mark as processed before executing handlers to prevent race conditions
    processedResults.current.add(resultKey);

    // Create the context for handlers
    const context: ToolHandlerContext = {
      result,
      message,
      isStreamingMessage,
      queryClient,
    };

    // Execute handlers
+   try {
      await toolHandlerRegistry.executeHandlers(context);
+   } catch (error) {
+     console.error(`Error executing handlers for ${result.toolInvocation.toolName}:`, error);
+     // Optionally, you might want to remove this result from processed results to retry later
+     // processedResults.current.delete(resultKey);
+   }

    // Log that we processed this result (for debugging)
    console.log(`Processed tool result: ${result.toolInvocation.toolName} (${resultKey})`);
  },
  [queryClient],
);
📝 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.

  const processToolResult = useCallback(
    async (result: ToolPart, message: Message, isStreamingMessage: boolean) => {
      // Generate a unique key for this tool result
      const resultKey = getToolResultKey(result);

      // Skip if we've already processed this exact result
      if (processedResults.current.has(resultKey)) {
        return;
      }

      // Mark as processed before executing handlers to prevent race conditions
      processedResults.current.add(resultKey);

      // Create the context for handlers
      const context: ToolHandlerContext = {
        result,
        message,
        isStreamingMessage,
        queryClient,
      };

      // Execute handlers
      try {
        await toolHandlerRegistry.executeHandlers(context);
      } catch (error) {
        console.error(`Error executing handlers for ${result.toolInvocation.toolName}:`, error);
        // Optionally, you might want to remove this result from processed results to retry later
        // processedResults.current.delete(resultKey);
      }

      // Log that we processed this result (for debugging)
      console.log(`Processed tool result: ${result.toolInvocation.toolName} (${resultKey})`);
    },
    [queryClient],
  );
apps/medusa/src/admin/routes/ai-chat/components/product-card.tsx (2)

71-71: ⚠️ Potential issue

Remove duplicate div with identical classes

There are nested divs with the same class names. Remove the duplicate to keep the DOM structure clean.

<div className="flex-1 min-w-0">
-  <div className="flex-1 min-w-0">
    <Heading level="h2" className="text-ui-fg-base text-sm line-clamp-2 min-h-10">
      {product.title}
    </Heading>
    {product.collection && (
      <Text size="small" className="text-ui-fg-subtle">
        {product.collection.title}
      </Text>
    )}
-  </div>
  <Text className="mt-2 text-ui-fg-base font-medium">{getPriceRange(product.variants)}</Text>
📝 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.

            <div className="flex-1 min-w-0">
                <Heading level="h2" className="text-ui-fg-base text-sm line-clamp-2 min-h-10">
                  {product.title}
                </Heading>
                {product.collection && (
                  <Text size="small" className="text-ui-fg-subtle">
                    {product.collection.title}
                  </Text>
                )}
                <Text className="mt-2 text-ui-fg-base font-medium">{getPriceRange(product.variants)}</Text>
            </div>

37-39: ⚠️ Potential issue

Ensure numeric values before mathematical operations

The code assumes that values in the prices array are numbers after filtering, but doesn't enforce this. Add type safety to ensure mathematical operations are performed on numbers.

- const minPrice = Math.min(...prices.map((p) => p.amount as number));
- const maxPrice = Math.max(...prices.map((p) => p.amount as number));
+ const validPrices = prices.map((p) => Number(p.amount)).filter((p) => !isNaN(p));
+ if (validPrices.length === 0) return '';
+ const minPrice = Math.min(...validPrices);
+ const maxPrice = Math.max(...validPrices);
📝 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.

  const validPrices = prices.map((p) => Number(p.amount)).filter((p) => !isNaN(p));
  if (validPrices.length === 0) return '';
  const minPrice = Math.min(...validPrices);
  const maxPrice = Math.max(...validPrices);
  const currency = prices[0].currency;
apps/medusa/src/admin/routes/ai-chat/components/ai-chat.tsx (1)

81-99: 🛠️ Refactor suggestion

Handle potential request or server-side errors.

While the onResponse callback handles success flows, consider capturing and handling errors (e.g., 4xx/5xx) or timeouts during chat submission. A try/catch or error boundary can provide a more robust user experience.

apps/medusa/src/admin/routes/ai-chat/components/chat-input.tsx (1)

4-4: ⚠️ Potential issue

Fix the typo in the icon import name.

There's a typo in the import name for the arrow icon: "ArrrowRight" has an extra "r".

-import { ArrrowRight } from '@medusajs/icons';
+import { ArrowRight } from '@medusajs/icons';

Also update the icon usage:

 <Button type="submit" variant="primary" disabled={isLoading}>
-  <ArrrowRight className="w-4 h-4" />
+  <ArrowRight className="w-4 h-4" />
 </Button>
📝 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.

// File: apps/medusa/src/admin/routes/ai-chat/components/chat-input.tsx

import { ArrowRight } from '@medusajs/icons';
// ... other imports

// ... component code

<Button type="submit" variant="primary" disabled={isLoading}>
  <ArrowRight className="w-4 h-4" />
</Button>

// ... remaining component code
apps/medusa/src/admin/routes/ai-chat/components/ToolResults/custom-handlers.ts (1)

90-102: ⚠️ Potential issue

Add toast notification for errors and fix condition check.

The error handler logs errors to the console but doesn't show user-visible notifications, unlike the success handlers. Also, the isStreamingMessage check is inconsistent with other handlers.

/**
 * Handler that shows an error toast for failed operations
 */
export const errorNotificationHandler: ToolHandler = (context: ToolHandlerContext) => {
  const { result, isStreamingMessage } = context;
-  if (!isStreamingMessage) return;
+  if (!isStreamingMessage || result.toolInvocation.state !== 'result') return;

  const { toolInvocation } = result;

  // Check if there was an error in the tool execution
-  if (toolInvocation.state === 'result' && toolInvocation.result && 'error' in toolInvocation.result) {
+  if (toolInvocation.result && 'error' in toolInvocation.result) {
    const errorMessage = toolInvocation.result.error as string;

    console.error('Error in tool execution:', errorMessage);
+    
+    // Show error toast to the user
+    toast.error('Operation Failed', {
+      description: typeof errorMessage === 'string' 
+        ? errorMessage 
+        : 'An error occurred during tool execution',
+    });
  }
};
📝 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.

/**
 * Handler that shows an error toast for failed operations
 */
export const errorNotificationHandler: ToolHandler = (context: ToolHandlerContext) => {
  const { result, isStreamingMessage } = context;
  if (!isStreamingMessage || result.toolInvocation.state !== 'result') return;

  const { toolInvocation } = result;

  // Check if there was an error in the tool execution
  if (toolInvocation.result && 'error' in toolInvocation.result) {
    const errorMessage = toolInvocation.result.error as string;

    console.error('Error in tool execution:', errorMessage);
    
    // Show error toast to the user
    toast.error('Operation Failed', {
      description: typeof errorMessage === 'string' 
        ? errorMessage 
        : 'An error occurred during tool execution',
    });
  }
};
apps/medusa/src/admin/routes/ai-chat/components/ToolResults/tool-handler-registry.ts (1)

60-98: ⚠️ Potential issue

Execution logic handles asynchronous operations well, but has a potential issue.

The handler execution logic correctly handles both synchronous and asynchronous handlers, but there's a minor concern.

Line 79 uses the same variable name result as the destructured property from context in line 65, which could lead to confusion. Also, there's no error handling for individual handlers, which might cause the entire execution to fail if one handler throws an exception.

  async executeHandlers(context: ToolHandlerContext): Promise<void> {
    const { result } = context;
    const toolName = result.toolInvocation.toolName;

    if (!toolName) return;

    // Handle prefix matches (e.g., "update-" matches "update-product" and "update-inventory")
    const matchingKeys = Array.from(this.handlers.keys()).filter((key) => toolName.startsWith(key) || toolName === key);

    const handlerPromises: Promise<void>[] = [];

    // Run specific handlers
    for (const key of matchingKeys) {
      const handlers = this.handlers.get(key) || [];
      handlers.forEach((handler) => {
-       const result = handler(context);
+       const handlerResult = handler(context);
-       if (result instanceof Promise) {
-         handlerPromises.push(result);
+       if (handlerResult instanceof Promise) {
+         handlerPromises.push(handlerResult.catch(error => {
+           console.error(`Handler error for tool ${toolName}:`, error);
+           // Optionally rethrow or handle as needed
+         }));
        }
      });
    }

    // Run wildcard handlers
    this.wildcardHandlers.forEach((handler) => {
-     const result = handler(context);
+     const handlerResult = handler(context);
-     if (result instanceof Promise) {
-       handlerPromises.push(result);
+     if (handlerResult instanceof Promise) {
+       handlerPromises.push(handlerResult.catch(error => {
+         console.error('Wildcard handler error:', error);
+         // Optionally rethrow or handle as needed
+       }));
      }
    });

    // Wait for all async handlers to complete
    if (handlerPromises.length > 0) {
      await Promise.all(handlerPromises);
    }
  }
📝 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.

  /**
   * Execute all relevant handlers for a given tool result
   * @param context The tool handler context
   */
  async executeHandlers(context: ToolHandlerContext): Promise<void> {
    const { result } = context;
    const toolName = result.toolInvocation.toolName;

    if (!toolName) return;

    // Handle prefix matches (e.g., "update-" matches "update-product" and "update-inventory")
    const matchingKeys = Array.from(this.handlers.keys()).filter((key) => toolName.startsWith(key) || toolName === key);

    const handlerPromises: Promise<void>[] = [];

    // Run specific handlers
    for (const key of matchingKeys) {
      const handlers = this.handlers.get(key) || [];
      handlers.forEach((handler) => {
-       const result = handler(context);
-       if (result instanceof Promise) {
-         handlerPromises.push(result);
+       const handlerResult = handler(context);
+       if (handlerResult instanceof Promise) {
+         handlerPromises.push(handlerResult.catch(error => {
+           console.error(`Handler error for tool ${toolName}:`, error);
+           // Optionally rethrow or handle as needed
+         }));
        }
      });
    }

    // Run wildcard handlers
    this.wildcardHandlers.forEach((handler) => {
-     const result = handler(context);
-     if (result instanceof Promise) {
-       handlerPromises.push(result);
+     const handlerResult = handler(context);
+     if (handlerResult instanceof Promise) {
+       handlerPromises.push(handlerResult.catch(error => {
+         console.error('Wildcard handler error:', error);
+         // Optionally rethrow or handle as needed
+       }));
        }
    });

    // Wait for all async handlers to complete
    if (handlerPromises.length > 0) {
      await Promise.all(handlerPromises);
    }
  }

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