-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: main
Are you sure you want to change the base?
Conversation
… scripts for improved data handling
…latform, including new fonts, color scheme, and responsive design adjustments
…, including updated branding, responsive design, and improved product metadata display
… into training-demo
…ing and add new product duration option in seed script
… into training-demo
… price information and enhance sorting functionality
WalkthroughThis 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
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
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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 rebrandingThe 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:
- Prevent loading issues if the external URL changes
- Improve page load performance
- Reduce dependency on external resources
apps/storefront/app/components/LogoStoreName/LogoStoreName.tsx (1)
18-20
: Consider keeping dynamic store name capabilityWhile 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 guidelinesThe 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 DemoThe 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 handlingThe 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 objectivesThis 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 componentThe 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 thereview
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 maintainabilityI 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
⛔ 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
tofont-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 offont-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 ofcreateCart
functionThis new function provides a clean way to create carts with authentication headers, making it reusable across the codebase.
74-93
: Improved cart creation flowThe refactored
addToCart
function streamlines the cart creation process by:
- Checking for an existing cart first
- Creating a new cart with the items directly if none exists
- 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 rebrandingThe 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 rebrandingThe 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 goodThe 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 identityThe change from
bg-accent-50
tobg-primary-800
with white text creates a more professional and distinct appearance, appropriate for a training platform.
49-54
: Content update aligns with rebrandingThe 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 appropriatelyThe 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 brandingThe font change from
font-aboreto
tofont-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 itemsThe 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 consistentlyThe change from
font-italiana
tofont-inter
for the section title maintains consistency with the new brand identity.
44-44
: Layout improvement with gridSwitching 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" | sortLength 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 cssLength 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 deploymentThis 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 overlayThe addition of
overlayOpacity
andoverlayColor
props improves the component's configurability, allowing for visual customization without modifying the component code.
25-27
: Good default values for new propsSetting 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 overlayThe 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 managementAdding 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" phaseYou 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 metricThere'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 utilityAdding the
formatPrice
utility is necessary for the enhanced price display functionality.
10-16
: Well-designed interface enhancement for pricing informationThe updated interface now includes comprehensive pricing properties (minPrice, maxPrice, exactPrice, discountPercentage), enabling more detailed product options display.
20-20
: Required addition of currencyCode propAdding the
currencyCode
prop is necessary for proper price formatting and display.
37-43
: Good implementation of price-based sortingSorting options by price (low to high) provides a consistent and user-friendly display order, improving the shopping experience.
44-71
: Comprehensive price display formattingThe 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 tsxLength 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 utilityAdding the
formatPrice
utility is necessary for the enhanced price display functionality.
11-18
: Well-designed interface enhancement for pricing informationThe 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 propAdding the
currencyCode
prop is necessary for proper price formatting and display.
35-46
: Good implementation of filtering and sortingThe 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 enhancementThe 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
andTrainingProductOptions
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 platformThe 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 contentThe 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 platformThe 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 fontGood 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-boldThe 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 designThe background property adjustments ensure proper inheritance between default and mobile contexts, maintaining consistent appearance across device sizes.
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 }); | ||
} | ||
}; |
There was a problem hiding this comment.
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 assumesshippingOptions
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.
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 }); | |
} | |
}; |
} 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"> (not available)</span>} | ||
{checked && <CheckCircleIcon className="text-primary-600 h-5 w-5" aria-hidden="true" />} | ||
</Label> | ||
)} | ||
</Radio> | ||
))} | ||
} |
There was a problem hiding this comment.
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.
…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>
There was a problem hiding this 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
andhandleOptionChangeByRadio
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
📒 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
withuseMemo
is an excellent improvement that:
- Prioritizes the first variant's options as defaults
- Falls back to first option values when needed
- Prevents unnecessary recalculations
- 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
forselectedVariant
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:
- Always showing all values for the first option (Duration)
- Adding price information to option values
- Filtering subsequent options based on previous selections
- 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
withdefaultValues.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
…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.
…a2-starter into updated-options-logic
- 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.
… into cart-drawer
feat(cart): refactor CartDrawer component and enhance useCart hook
…logic feat(product-options): enhance product option selectors with price formatting and sorting
There was a problem hiding this 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[]
foritems
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 thecart
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
📒 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.
There was a problem hiding this 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
📒 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.
There was a problem hiding this 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 logicThe 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 effectThis 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 productionDebug 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 managementYou're maintaining local state (
isOpen
) that mirrors the external state (cartDrawerOpen
), which adds complexity.Since the
Dialog
component from Headless UI accepts anopen
prop directly, consider usingcartDrawerOpen
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 propsWhile 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
📒 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 commentsThe 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 useRefUsing
useState
for UI state anduseRef
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 organizationRefactoring 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 implementationGood optimization by only updating the state when it's actually changing. This prevents unnecessary re-renders and improves performance.
114-117
: Good cleanup practiceProperly cleaning up timers on component unmount is important to prevent memory leaks. This follows React best practices.
119-120
: Clear and concise derived state logicThe 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 objectThe 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 CartDrawerHeaderBreaking 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 componentsThe 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 componentsBreaking 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 componentThe 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 componentsThe use of
DialogBackdrop
andDialogPanel
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.
There was a problem hiding this 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 propsWhile 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 deploymentThe 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
📒 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 considerationsThe
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 statesThe 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 handlingThe
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 stateUsing
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 patternThe 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 handlersUsing
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 transitionsThe 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 footerThe 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.
There was a problem hiding this 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 asany[]
. 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, theitems
prop should have a more specific type thanany[]
.-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 usingany
.- 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
📒 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.
There was a problem hiding this 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
📒 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.
Add reviews and AI MCP to training demo - need env variables
There was a problem hiding this 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 issueIncomplete 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 issueInconsistent variant handling between update and create functions
The
transformProductForUpdate
function transforms variants using thetransformVariant
function, buttransformProductForCreate
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 issueAdd 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 issueRemove "(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 issueAvoid 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 tsLength 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 tsLength 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 expectedUpsertProductVariantDTO
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 tsLength of output: 1990
Resolve Type Mismatch in Workflow Input
The call to
updateProductVariantsWorkflow
is still causing a TypeScript error because the payload provided in theinput
field doesn’t conform to theUpsertProductVariantDTO
definition (seeapps/medusa/src/api/mcp/servers/medusa/tools/products/product.types.ts
). Please verify and update the structure of thevariants
variable so that each variant fully complies with the expected type (for example, ensuring any required fields or optional properties likeid
are properly handled).
- Action Points:
- Check that each object in
variants
matches theUpsertProductVariantDTO
interface, which extendsUpdateProductVariantDTO
.- 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/testsLength 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/srcLength 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 (seeapps/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 issueAddress the type mismatch in variant creation.
The pipeline indicates that
inventory_quantity
andmanage_inventory
are not recognized properties in the expectedCreateProductVariantDTO
. You can move these values into theinventory_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:
- Move working tests to the appropriate folder without "TODO"
- 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 -lLength 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 issueRemove 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 issueRemove 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 issueRemove 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 broadcatch
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 issuePossible confusion with input structure vs. usage.
Although this interface defines typical address fields, the code later referenceslocation_id
anditem.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 issueMismatch between interface and usage.
The code referencesitem.location_id
anditem.address
, but theUpsertStockLocationAddressInput
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 nestedaddress
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
'svalidateAndTransformBody
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.4This version alignment ensures proper type checking and middleware functionality when using
validateAndTransformBody
and related validation utilities[4][7].Citations:
- 1: https://docs.medusajs.com/learn/fundamentals/api-routes/validation
- 2: https://docs.medusajs.com/resources/examples/guides/quote-management
- 3: https://github.com/colinhacks/zod
- 4: [Bug]: Upgraded to 2.4 and issue with zod and middlewares medusajs/medusa#11380
- 5: https://docs.medusajs.com/resources/plugins/guides/wishlist
- 6: [Bug]: possible zod validation bug medusajs/medusa#11355
- 7: https://docs.medusajs.com/resources/examples
- 8: https://docs.novu.co/framework/schema/zod
- 9: https://docs.medusajs.com/learn/customization/custom-features/api-route
- 10: https://mygit.osfipin.com/release/179840295
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.4Please 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 issueFix 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:
TS2345: Argument of type 'ZodObject<...>' is not assignable to parameter of type 'AnyZodObject'
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 forreview.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 issueFix TypeScript errors in query schema definition
The build pipeline is failing with the same TypeScript errors as in the product-reviews middleware file:
TS2345: Argument of type 'ZodObject<...>' is not assignable to parameter of type 'AnyZodObject'
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 issueFix 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:
- Feature Detection:
Prefer detecting specific browser capabilities (e.g.,'serviceWorker' in navigator
) rather than platform checks[1][6].- 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:
- 1: Issue with "Navigator.platform": Cross-browser API marked deprecated and Chromium-only alternative suggested mdn/content#14429
- 2: https://www.softwareworld.co/competitors/waypoint-navigator-alternatives/
- 3: https://developer.mozilla.org/en-US/docs/Web/API/Navigator/vendor
- 4: https://groups.google.com/g/mozilla.dev.mdc/c/tIx2iiH2u3o
- 5: https://www.trustradius.com/products/aimms-sc-navigator-platform/competitors
- 6: https://erikmartinjordan.com/navigator-platform-deprecated-alternative
- 7: https://www.w3schools.com/jsref/prop_nav_platform.asp
- 8: https://www.gartner.com/reviews/market/non-life-insurance-core-platforms-europe/vendor/coforge/product/navigator/alternatives
- 9: https://www.w3schools.com/js/js_window_navigator.asp
- 10: Deprecation of navigator.userAgent, navigator.appVersion, and navigator.platform typekit/webfontloader#451
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 issueAvoid 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 contextCommittable 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 codeAnd 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 issueRemove 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 issueEnsure 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 issueFix 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 issueAdd 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 issueExecution 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); } }
Summary by CodeRabbit
New Features
Style & UI Improvements
Other Updates