@thorprovider/create-storefront 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +119 -0
  2. package/bin/install.js +116 -0
  3. package/commands/sf-add-view.md +21 -0
  4. package/commands/sf-init.md +16 -0
  5. package/commands/sf-theme.md +15 -0
  6. package/commands/sf-view.md +20 -0
  7. package/package.json +40 -0
  8. package/recipes/archetype.schema.json +39 -0
  9. package/recipes/archetypes.json +148 -0
  10. package/recipes/recipe.schema.json +59 -0
  11. package/recipes/recipes.json +90 -0
  12. package/recipes/sections.json +46 -0
  13. package/recipes/validate.mjs +190 -0
  14. package/skills/building-storefronts/SKILL.md +178 -0
  15. package/skills/building-storefronts/references/frontend-integration.md +229 -0
  16. package/skills/json-render-core/SKILL.md +291 -0
  17. package/skills/json-render-next/SKILL.md +194 -0
  18. package/skills/json-render-react/SKILL.md +298 -0
  19. package/skills/json-render-remotion/SKILL.md +111 -0
  20. package/skills/json-render-shadcn/SKILL.md +159 -0
  21. package/skills/json-render-solid/SKILL.md +204 -0
  22. package/skills/nextjs-shadcn/SKILL.md +303 -0
  23. package/skills/nextjs-shadcn/references/architecture.md +499 -0
  24. package/skills/nextjs-shadcn/references/project-setup.md +127 -0
  25. package/skills/nextjs-shadcn/references/shadcn-platform.md +258 -0
  26. package/skills/nextjs-shadcn/references/sidebar.md +274 -0
  27. package/skills/nextjs-shadcn/references/styling.md +555 -0
  28. package/skills/sf-scaffold/SKILL.md +118 -0
  29. package/skills/sf-theme-gen/SKILL.md +44 -0
  30. package/skills/sf-view-gen/SKILL.md +94 -0
  31. package/skills/shadcn-component-discovery/SKILL.md +273 -0
  32. package/skills/shadcn-component-discovery/references/registries.md +226 -0
  33. package/skills/shadcn-theming/SKILL.md +104 -0
  34. package/skills/shadcn-theming/references/templates/theme-setup.md +109 -0
  35. package/skills/shadcn-theming/references/theming-guide.md +90 -0
  36. package/skills/storefront-best-practices/SKILL.md +421 -0
  37. package/skills/storefront-best-practices/reference/components/breadcrumbs.md +123 -0
  38. package/skills/storefront-best-practices/reference/components/cart-popup.md +189 -0
  39. package/skills/storefront-best-practices/reference/components/country-selector.md +298 -0
  40. package/skills/storefront-best-practices/reference/components/footer.md +112 -0
  41. package/skills/storefront-best-practices/reference/components/hero.md +241 -0
  42. package/skills/storefront-best-practices/reference/components/megamenu.md +239 -0
  43. package/skills/storefront-best-practices/reference/components/navbar.md +397 -0
  44. package/skills/storefront-best-practices/reference/components/popups.md +221 -0
  45. package/skills/storefront-best-practices/reference/components/product-card.md +125 -0
  46. package/skills/storefront-best-practices/reference/components/product-reviews.md +217 -0
  47. package/skills/storefront-best-practices/reference/components/product-slider.md +174 -0
  48. package/skills/storefront-best-practices/reference/components/search.md +101 -0
  49. package/skills/storefront-best-practices/reference/connecting-to-backend.md +391 -0
  50. package/skills/storefront-best-practices/reference/design.md +388 -0
  51. package/skills/storefront-best-practices/reference/features/promotions.md +307 -0
  52. package/skills/storefront-best-practices/reference/features/wishlist.md +230 -0
  53. package/skills/storefront-best-practices/reference/layouts/account.md +380 -0
  54. package/skills/storefront-best-practices/reference/layouts/cart.md +316 -0
  55. package/skills/storefront-best-practices/reference/layouts/checkout.md +486 -0
  56. package/skills/storefront-best-practices/reference/layouts/home-page.md +264 -0
  57. package/skills/storefront-best-practices/reference/layouts/order-confirmation.md +231 -0
  58. package/skills/storefront-best-practices/reference/layouts/product-details.md +527 -0
  59. package/skills/storefront-best-practices/reference/layouts/product-listing.md +520 -0
  60. package/skills/storefront-best-practices/reference/layouts/static-pages.md +356 -0
  61. package/skills/storefront-best-practices/reference/medusa.md +307 -0
  62. package/skills/storefront-best-practices/reference/mobile-responsiveness.md +183 -0
  63. package/skills/storefront-best-practices/reference/seo.md +195 -0
  64. package/templates/app/app/[[...slug]]/page.tsx +17 -0
  65. package/templates/app/app/[[...slug]]/renderer.tsx +10 -0
  66. package/templates/app/app/globals.css +101 -0
  67. package/templates/app/app/layout.tsx +35 -0
  68. package/templates/app/lib/__STOREFRONT__/catalog.ts +132 -0
  69. package/templates/app/lib/__STOREFRONT__/handlers.ts +33 -0
  70. package/templates/app/lib/__STOREFRONT__/registry.tsx +134 -0
  71. package/templates/app/lib/__STOREFRONT__/runtime.ts +25 -0
  72. package/templates/app/lib/__STOREFRONT__/spec/home.ts +62 -0
  73. package/templates/app/lib/__STOREFRONT__/spec/index.ts +59 -0
  74. package/templates/app/lib/__STOREFRONT__/spec/types.ts +14 -0
  75. package/templates/app/lib/__STOREFRONT__/state.ts +35 -0
@@ -0,0 +1,90 @@
1
+ {
2
+ "$schema": "./recipe.schema.json",
3
+ "version": "1.0.0",
4
+ "views": [
5
+ {
6
+ "name": "home",
7
+ "description": "Storefront landing page with hero, benefits, and category highlights",
8
+ "route": "/",
9
+ "loader": "home",
10
+ "requiredDataKeys": ["heroProducts", "categories"],
11
+ "defaultVariants": {
12
+ "hero": "StorefrontHero1",
13
+ "benefits": "Feature1",
14
+ "categories": "ProductCategory2"
15
+ },
16
+ "allowedVariants": {
17
+ "hero": ["StorefrontHero1", "StorefrontHero2", "StorefrontHero5", "StorefrontHero6"],
18
+ "benefits": ["Feature1", "Feature2"],
19
+ "categories": ["ProductCategory1", "ProductCategory2", "ProductCategory4", "ProductCategory5"],
20
+ "productCard": ["ProductCard1", "ProductCard2", "ProductCard3", "ProductCard5"]
21
+ },
22
+ "optionalSections": ["testimonials", "faq", "statistics", "logoCloud", "pricing"]
23
+ },
24
+ {
25
+ "name": "product",
26
+ "description": "Single product detail page with overview, gallery, and reviews",
27
+ "route": "/productos/[id]",
28
+ "loader": "product",
29
+ "requiredDataKeys": ["selectedProduct"],
30
+ "defaultVariants": {
31
+ "overview": "ProductOverview1",
32
+ "reviews": "ReviewRating1"
33
+ },
34
+ "allowedVariants": {
35
+ "overview": ["ProductOverview1", "ProductOverview2", "ProductOverview5"],
36
+ "reviews": ["ReviewRating1", "ReviewRating2"],
37
+ "productCard": ["ProductCard1", "ProductCard2", "ProductCard3", "ProductCard5"]
38
+ },
39
+ "optionalSections": ["testimonials", "faq", "statistics"]
40
+ },
41
+ {
42
+ "name": "category",
43
+ "description": "Product listing filtered by category with filters and sorting",
44
+ "route": "/categorias/[[...slug]]",
45
+ "loader": "category",
46
+ "requiredDataKeys": ["categoryProducts", "storeCategories"],
47
+ "defaultVariants": {
48
+ "filter": "CategoryFilter2",
49
+ "productCard": "ProductCard1"
50
+ },
51
+ "allowedVariants": {
52
+ "filter": ["CategoryFilter1", "CategoryFilter2", "CategoryFilter4"],
53
+ "productCard": ["ProductCard1", "ProductCard2", "ProductCard3", "ProductCard5"]
54
+ },
55
+ "optionalSections": ["logoCloud", "testimonials"]
56
+ },
57
+ {
58
+ "name": "checkout",
59
+ "description": "Multi-step checkout with cart summary, shipping, and payment",
60
+ "route": "/checkout",
61
+ "loader": "checkout",
62
+ "requiredDataKeys": ["checkoutCountries", "checkoutPaymentMethods"],
63
+ "defaultVariants": {
64
+ "checkout": "CheckoutForm1",
65
+ "confirmation": "CheckoutConfirmation"
66
+ },
67
+ "allowedVariants": {
68
+ "checkout": ["CheckoutForm1", "CheckoutForm2", "CheckoutForm3"],
69
+ "confirmation": ["CheckoutConfirmation"]
70
+ },
71
+ "optionalSections": []
72
+ },
73
+ {
74
+ "name": "user-panel",
75
+ "description": "Customer account area with order history and account navigation",
76
+ "route": "/cuenta",
77
+ "loader": "userPanel",
78
+ "requiredDataKeys": ["lastOrder", "lastOrderLines", "orderPlaced"],
79
+ "defaultVariants": {
80
+ "shell": "AppShell1",
81
+ "orderHistory": "OrderHistory1"
82
+ },
83
+ "allowedVariants": {
84
+ "shell": ["AppShell1"],
85
+ "orderHistory": ["OrderHistory1", "OrderHistory2"]
86
+ },
87
+ "optionalSections": []
88
+ }
89
+ ]
90
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "$schema": "./section.schema.json",
3
+ "version": "1.0.0",
4
+ "sections": [
5
+ {
6
+ "name": "testimonials",
7
+ "description": "Customer testimonials or social proof",
8
+ "elementType": "Testimonials",
9
+ "variants": ["Testimonial1", "Testimonial2"],
10
+ "defaultVariant": "Testimonial1",
11
+ "propsHint": "title, badge, items[] (quote, author, role, avatar)"
12
+ },
13
+ {
14
+ "name": "faq",
15
+ "description": "Frequently asked questions accordion",
16
+ "elementType": "Faq",
17
+ "variants": ["FaqBlock1"],
18
+ "defaultVariant": "FaqBlock1",
19
+ "propsHint": "title, description, items[] (question, answer)"
20
+ },
21
+ {
22
+ "name": "statistics",
23
+ "description": "Key metrics or trust statistics",
24
+ "elementType": "Statistics",
25
+ "variants": ["Statistic1"],
26
+ "defaultVariant": "Statistic1",
27
+ "propsHint": "title, items[] (value, label)"
28
+ },
29
+ {
30
+ "name": "logoCloud",
31
+ "description": "Brand or partner logo strip",
32
+ "elementType": "LogoCloud",
33
+ "variants": ["LogoCloud1"],
34
+ "defaultVariant": "LogoCloud1",
35
+ "propsHint": "title, platforms[]/logos[] (name, src)"
36
+ },
37
+ {
38
+ "name": "pricing",
39
+ "description": "Pricing tiers or plans",
40
+ "elementType": "Pricing",
41
+ "variants": ["PricingSection1"],
42
+ "defaultVariant": "PricingSection1",
43
+ "propsHint": "title, description, tiers[] (name, price, features, cta)"
44
+ }
45
+ ]
46
+ }
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs'
3
+ import { dirname, join } from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ const here = dirname(fileURLToPath(import.meta.url))
7
+
8
+ const recipes = JSON.parse(readFileSync(join(here, 'recipes.json'), 'utf8'))
9
+ const sections = JSON.parse(readFileSync(join(here, 'sections.json'), 'utf8'))
10
+ const archetypes = JSON.parse(readFileSync(join(here, 'archetypes.json'), 'utf8'))
11
+
12
+ export function loadRecipes() {
13
+ return recipes
14
+ }
15
+
16
+ export function loadArchetypes() {
17
+ return archetypes
18
+ }
19
+
20
+ /**
21
+ * Resolve a slot id ("<view>.<slot>") to its archetype definition, or null.
22
+ */
23
+ export function getSlotArchetype(viewName, slot) {
24
+ return archetypes.slots[`${viewName}.${slot}`] ?? null
25
+ }
26
+
27
+ /**
28
+ * Resolve an intent name to its block variant for a given view + slot.
29
+ * Returns { ok, variant } or { ok: false, errors }.
30
+ */
31
+ export function resolveIntent(viewName, slot, intentName) {
32
+ const errors = []
33
+ const slotArchetype = getSlotArchetype(viewName, slot)
34
+ if (!slotArchetype) {
35
+ errors.push(`Slot "${slot}" has no archetype for view "${viewName}".`)
36
+ return { ok: false, errors }
37
+ }
38
+ const intent = slotArchetype.intents[intentName]
39
+ if (!intent) {
40
+ errors.push(
41
+ `Unknown intent "${intentName}" for slot "${slot}" in view "${viewName}". Allowed intents: ${Object.keys(slotArchetype.intents).join(', ')}.`
42
+ )
43
+ return { ok: false, errors }
44
+ }
45
+ if (!isAllowedVariant(viewName, slot, intent.variant)) {
46
+ const allowed = getRecipe(viewName)?.allowedVariants[slot] ?? []
47
+ errors.push(
48
+ `Intent "${intentName}" resolves to variant "${intent.variant}", which is not allowed for slot "${slot}" in view "${viewName}". Allowed variants: ${allowed.join(', ') || '(none)'}.`
49
+ )
50
+ return { ok: false, errors }
51
+ }
52
+ return { ok: true, variant: intent.variant }
53
+ }
54
+
55
+ export function loadSections() {
56
+ return sections
57
+ }
58
+
59
+ export function getRecipe(name) {
60
+ return recipes.views.find((v) => v.name === name) ?? null
61
+ }
62
+
63
+ export function allowedViewNames() {
64
+ return recipes.views.map((v) => v.name)
65
+ }
66
+
67
+ export function isAllowedVariant(viewName, slot, variant) {
68
+ const recipe = getRecipe(viewName)
69
+ if (!recipe) return false
70
+ const allowed = recipe.allowedVariants[slot]
71
+ if (!allowed) return false
72
+ return allowed.includes(variant)
73
+ }
74
+
75
+ export function isAllowedSection(viewName, sectionName) {
76
+ const recipe = getRecipe(viewName)
77
+ if (!recipe) return false
78
+ if (!recipe.optionalSections.includes(sectionName)) return false
79
+ return sections.sections.some((s) => s.name === sectionName)
80
+ }
81
+
82
+ /**
83
+ * Validate a view composition against its recipe.
84
+ * composition: { view: string, variants?: {slot: variant}, intents?: {slot: intentName}, sections?: [{name, variant}] }
85
+ * Throws on any undeclared view, variant, intent, or section.
86
+ */
87
+ export function validateComposition(composition) {
88
+ const errors = []
89
+ const recipe = getRecipe(composition.view)
90
+ if (!recipe) {
91
+ errors.push(
92
+ `Unknown view "${composition.view}". Allowed views: ${allowedViewNames().join(', ')}.`
93
+ )
94
+ return { ok: false, errors }
95
+ }
96
+
97
+ for (const [slot, intentName] of Object.entries(composition.intents ?? {})) {
98
+ const resolved = resolveIntent(composition.view, slot, intentName)
99
+ if (!resolved.ok) errors.push(...resolved.errors)
100
+ }
101
+
102
+ for (const [slot, variant] of Object.entries(composition.variants ?? {})) {
103
+ if (!isAllowedVariant(composition.view, slot, variant)) {
104
+ const allowed = recipe.allowedVariants[slot] ?? []
105
+ errors.push(
106
+ `Variant "${variant}" is not allowed for slot "${slot}" in view "${composition.view}". Allowed: ${allowed.join(', ') || '(none)'}.`
107
+ )
108
+ }
109
+ }
110
+
111
+ for (const section of composition.sections ?? []) {
112
+ if (!isAllowedSection(composition.view, section.name)) {
113
+ errors.push(
114
+ `Section "${section.name}" is not allowed in view "${composition.view}". Allowed: ${recipe.optionalSections.join(', ') || '(none)'}.`
115
+ )
116
+ continue
117
+ }
118
+ if (section.variant) {
119
+ const def = sections.sections.find((s) => s.name === section.name)
120
+ if (def && !def.variants.includes(section.variant)) {
121
+ errors.push(
122
+ `Section variant "${section.variant}" is not allowed for section "${section.name}". Allowed: ${def.variants.join(', ')}.`
123
+ )
124
+ }
125
+ }
126
+ }
127
+
128
+ return { ok: errors.length === 0, errors }
129
+ }
130
+
131
+ function main() {
132
+ const [, , command, ...rest] = process.argv
133
+
134
+ if (command === 'list') {
135
+ console.log(allowedViewNames().join('\n'))
136
+ return
137
+ }
138
+
139
+ if (command === 'show') {
140
+ const recipe = getRecipe(rest[0])
141
+ if (!recipe) {
142
+ console.error(`Unknown view "${rest[0]}". Allowed: ${allowedViewNames().join(', ')}`)
143
+ process.exit(1)
144
+ }
145
+ console.log(JSON.stringify(recipe, null, 2))
146
+ return
147
+ }
148
+
149
+ if (command === 'validate') {
150
+ const arg = rest.join(' ').trim()
151
+ const payload = arg.startsWith('{')
152
+ ? JSON.parse(arg)
153
+ : arg
154
+ ? JSON.parse(readFileSync(arg, 'utf8'))
155
+ : {}
156
+ const result = validateComposition(payload)
157
+ if (!result.ok) {
158
+ console.error(result.errors.join('\n'))
159
+ process.exit(1)
160
+ }
161
+ console.log('OK')
162
+ return
163
+ }
164
+
165
+ if (command === 'resolve') {
166
+ // Usage: resolve <view> <slot> <intent>
167
+ const [viewName, slot, intentName] = rest
168
+ const resolved = resolveIntent(viewName, slot, intentName)
169
+ if (!resolved.ok) {
170
+ console.error(resolved.errors.join('\n'))
171
+ process.exit(1)
172
+ }
173
+ console.log(resolved.variant)
174
+ return
175
+ }
176
+
177
+ if (command === 'archetypes') {
178
+ console.log(JSON.stringify(archetypes.slots, null, 2))
179
+ return
180
+ }
181
+
182
+ console.error(
183
+ 'Usage: validate.mjs list | show <view> | validate <composition.json> | resolve <view> <slot> <intent> | archetypes'
184
+ )
185
+ process.exit(1)
186
+ }
187
+
188
+ if (import.meta.url === `file://${process.argv[1]}`) {
189
+ main()
190
+ }
@@ -0,0 +1,178 @@
1
+ ---
2
+ name: building-storefronts
3
+ description: Load automatically when planning, researching, or implementing Medusa storefront features (calling custom API routes, SDK integration, React Query patterns, data fetching). REQUIRED for all storefront development in ALL modes (planning, implementation, exploration). Contains SDK usage patterns, frontend integration, and critical rules for calling Medusa APIs.
4
+ ---
5
+
6
+ # Medusa Storefront Development
7
+
8
+ Frontend integration guide for building storefronts with Medusa. Covers SDK usage, React Query patterns, and calling custom API routes.
9
+
10
+ ## When to Apply
11
+
12
+ **Load this skill for ANY storefront development task, including:**
13
+ - Calling custom Medusa API routes from the storefront
14
+ - Integrating Medusa SDK in frontend applications
15
+ - Using React Query for data fetching
16
+ - Implementing mutations with optimistic updates
17
+ - Error handling and cache invalidation
18
+
19
+ **Also load building-with-medusa when:** Building the backend API routes that the storefront calls
20
+
21
+ ## CRITICAL: Load Reference Files When Needed
22
+
23
+ **The quick reference below is NOT sufficient for implementation.** You MUST load the reference file before writing storefront integration code.
24
+
25
+ **Load this reference when implementing storefront features:**
26
+
27
+ - **Calling API routes?** → MUST load `references/frontend-integration.md` first
28
+ - **Using SDK?** → MUST load `references/frontend-integration.md` first
29
+ - **Implementing React Query?** → MUST load `references/frontend-integration.md` first
30
+
31
+ ## Rule Categories by Priority
32
+
33
+ | Priority | Category | Impact | Prefix |
34
+ |----------|----------|--------|--------|
35
+ | 1 | SDK Usage | CRITICAL | `sdk-` |
36
+ | 2 | React Query Patterns | HIGH | `query-` |
37
+ | 3 | Data Display | HIGH (includes CRITICAL price rule) | `display-` |
38
+ | 4 | Error Handling | MEDIUM | `error-` |
39
+
40
+ ## Quick Reference
41
+
42
+ ### 1. SDK Usage (CRITICAL)
43
+
44
+ - `sdk-always-use` - **ALWAYS use the Medusa JS SDK for ALL API requests** - NEVER use regular fetch()
45
+ - `sdk-existing-methods` - For built-in endpoints, use existing SDK methods (`sdk.store.product.list()`, `sdk.admin.order.retrieve()`)
46
+ - `sdk-client-fetch` - For custom API routes, use `sdk.client.fetch()`
47
+ - `sdk-required-headers` - SDK automatically adds required headers (publishable API key for store, auth for admin) - regular fetch() missing these headers causes errors
48
+ - `sdk-no-json-stringify` - **NEVER use JSON.stringify() on body** - SDK handles serialization automatically
49
+ - `sdk-plain-objects` - Pass plain JavaScript objects to body, not strings
50
+ - `sdk-locate-first` - Always locate where SDK is instantiated in the project before using it
51
+
52
+ ### 2. React Query Patterns (HIGH)
53
+
54
+ - `query-use-query` - Use `useQuery` for GET requests (data fetching)
55
+ - `query-use-mutation` - Use `useMutation` for POST/DELETE requests (mutations)
56
+ - `query-invalidate` - Invalidate queries in `onSuccess` to refresh data after mutations
57
+ - `query-keys-hierarchical` - Structure query keys hierarchically for effective cache management
58
+ - `query-loading-states` - Always handle `isLoading`, `isPending`, `isError` states
59
+
60
+ ### 3. Data Display (HIGH)
61
+
62
+ - `display-price-format` - **CRITICAL**: Prices from Medusa are stored as-is ($49.99 = 49.99, NOT in cents). Display them directly - NEVER divide by 100
63
+
64
+ ### 4. Error Handling (MEDIUM)
65
+
66
+ - `error-on-error` - Implement `onError` callback in mutations to handle failures
67
+ - `error-display` - Show error messages to users when mutations fail
68
+ - `error-rollback` - Use optimistic updates with rollback on error for better UX
69
+
70
+ ## Critical SDK Pattern
71
+
72
+ **ALWAYS pass plain objects to the SDK - NEVER use JSON.stringify():**
73
+
74
+ ```typescript
75
+ // ✅ CORRECT - Plain object
76
+ await sdk.client.fetch("/store/reviews", {
77
+ method: "POST",
78
+ body: {
79
+ product_id: "prod_123",
80
+ rating: 5,
81
+ }
82
+ })
83
+
84
+ // ❌ WRONG - JSON.stringify breaks the request
85
+ await sdk.client.fetch("/store/reviews", {
86
+ method: "POST",
87
+ body: JSON.stringify({ // ❌ DON'T DO THIS!
88
+ product_id: "prod_123",
89
+ rating: 5,
90
+ })
91
+ })
92
+ ```
93
+
94
+ **Why this matters:**
95
+ - The SDK handles JSON serialization automatically
96
+ - Using JSON.stringify() will double-serialize and break the request
97
+ - The server won't be able to parse the body
98
+
99
+ ## Common Mistakes Checklist
100
+
101
+ Before implementing, verify you're NOT doing these:
102
+
103
+ **SDK Usage:**
104
+ - [ ] Using regular fetch() instead of the Medusa JS SDK (causes missing header errors)
105
+ - [ ] Not using existing SDK methods for built-in endpoints (e.g., using sdk.client.fetch("/store/products") instead of sdk.store.product.list())
106
+ - [ ] Using JSON.stringify() on the body parameter
107
+ - [ ] Manually setting Content-Type headers (SDK adds them)
108
+ - [ ] Hardcoding SDK import paths (locate in project first)
109
+ - [ ] Not using sdk.client.fetch() for custom routes
110
+
111
+ **React Query:**
112
+ - [ ] Not invalidating queries after mutations
113
+ - [ ] Using flat query keys instead of hierarchical
114
+ - [ ] Not handling loading and error states
115
+ - [ ] Forgetting to disable buttons during mutations (isPending)
116
+
117
+ **Data Display:**
118
+ - [ ] **CRITICAL**: Dividing prices by 100 when displaying (prices are stored as-is: $49.99 = 49.99, NOT in cents)
119
+
120
+ **Error Handling:**
121
+ - [ ] Not implementing onError callbacks
122
+ - [ ] Not showing error messages to users
123
+ - [ ] Not handling network failures gracefully
124
+
125
+ ## How to Use
126
+
127
+ **For detailed patterns and examples, load reference file:**
128
+
129
+ ```
130
+ references/frontend-integration.md - SDK usage, React Query patterns, API integration
131
+ ```
132
+
133
+ The reference file contains:
134
+ - Step-by-step SDK integration patterns
135
+ - Complete React Query examples
136
+ - Correct vs incorrect code examples
137
+ - Query key best practices
138
+ - Optimistic update patterns
139
+ - Error handling strategies
140
+
141
+ ## When to Use MedusaDocs MCP Server
142
+
143
+ **Use this skill for (PRIMARY SOURCE):**
144
+ - How to call custom API routes from storefront
145
+ - SDK usage patterns (sdk.client.fetch)
146
+ - React Query integration patterns
147
+ - Common mistakes and anti-patterns
148
+
149
+ **Use MedusaDocs MCP server for (SECONDARY SOURCE):**
150
+ - Built-in SDK methods (sdk.admin.*, sdk.store.*)
151
+ - Official Medusa SDK API reference
152
+ - Framework-specific configuration options
153
+
154
+ **Why skills come first:**
155
+ - Skills contain critical patterns like "don't use JSON.stringify" that MCP doesn't emphasize
156
+ - Skills show correct vs incorrect patterns; MCP shows what's possible
157
+ - Planning requires understanding patterns, not just API reference
158
+
159
+ ## Integration with Backend
160
+
161
+ **⚠️ CRITICAL: ALWAYS use the Medusa JS SDK - NEVER use regular fetch()**
162
+
163
+ When building features that span backend and frontend:
164
+
165
+ 1. **Backend (building-with-medusa skill):** Module → Workflow → API Route
166
+ 2. **Storefront (this skill):** SDK → React Query → UI Components
167
+ 3. **Connection:**
168
+ - Built-in endpoints: Use existing SDK methods (`sdk.store.product.list()`)
169
+ - Custom API routes: Use `sdk.client.fetch("/store/my-route")`
170
+ - **NEVER use regular fetch()** - missing publishable API key causes errors
171
+
172
+ **Why the SDK is required:**
173
+ - Store routes need `x-publishable-api-key` header
174
+ - Admin routes need `Authorization` and session headers
175
+ - SDK handles all required headers automatically
176
+ - Regular fetch() without headers → authentication/authorization errors
177
+
178
+ See `building-with-medusa` for backend API route patterns.