@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.
- package/README.md +119 -0
- package/bin/install.js +116 -0
- package/commands/sf-add-view.md +21 -0
- package/commands/sf-init.md +16 -0
- package/commands/sf-theme.md +15 -0
- package/commands/sf-view.md +20 -0
- package/package.json +40 -0
- package/recipes/archetype.schema.json +39 -0
- package/recipes/archetypes.json +148 -0
- package/recipes/recipe.schema.json +59 -0
- package/recipes/recipes.json +90 -0
- package/recipes/sections.json +46 -0
- package/recipes/validate.mjs +190 -0
- package/skills/building-storefronts/SKILL.md +178 -0
- package/skills/building-storefronts/references/frontend-integration.md +229 -0
- package/skills/json-render-core/SKILL.md +291 -0
- package/skills/json-render-next/SKILL.md +194 -0
- package/skills/json-render-react/SKILL.md +298 -0
- package/skills/json-render-remotion/SKILL.md +111 -0
- package/skills/json-render-shadcn/SKILL.md +159 -0
- package/skills/json-render-solid/SKILL.md +204 -0
- package/skills/nextjs-shadcn/SKILL.md +303 -0
- package/skills/nextjs-shadcn/references/architecture.md +499 -0
- package/skills/nextjs-shadcn/references/project-setup.md +127 -0
- package/skills/nextjs-shadcn/references/shadcn-platform.md +258 -0
- package/skills/nextjs-shadcn/references/sidebar.md +274 -0
- package/skills/nextjs-shadcn/references/styling.md +555 -0
- package/skills/sf-scaffold/SKILL.md +118 -0
- package/skills/sf-theme-gen/SKILL.md +44 -0
- package/skills/sf-view-gen/SKILL.md +94 -0
- package/skills/shadcn-component-discovery/SKILL.md +273 -0
- package/skills/shadcn-component-discovery/references/registries.md +226 -0
- package/skills/shadcn-theming/SKILL.md +104 -0
- package/skills/shadcn-theming/references/templates/theme-setup.md +109 -0
- package/skills/shadcn-theming/references/theming-guide.md +90 -0
- package/skills/storefront-best-practices/SKILL.md +421 -0
- package/skills/storefront-best-practices/reference/components/breadcrumbs.md +123 -0
- package/skills/storefront-best-practices/reference/components/cart-popup.md +189 -0
- package/skills/storefront-best-practices/reference/components/country-selector.md +298 -0
- package/skills/storefront-best-practices/reference/components/footer.md +112 -0
- package/skills/storefront-best-practices/reference/components/hero.md +241 -0
- package/skills/storefront-best-practices/reference/components/megamenu.md +239 -0
- package/skills/storefront-best-practices/reference/components/navbar.md +397 -0
- package/skills/storefront-best-practices/reference/components/popups.md +221 -0
- package/skills/storefront-best-practices/reference/components/product-card.md +125 -0
- package/skills/storefront-best-practices/reference/components/product-reviews.md +217 -0
- package/skills/storefront-best-practices/reference/components/product-slider.md +174 -0
- package/skills/storefront-best-practices/reference/components/search.md +101 -0
- package/skills/storefront-best-practices/reference/connecting-to-backend.md +391 -0
- package/skills/storefront-best-practices/reference/design.md +388 -0
- package/skills/storefront-best-practices/reference/features/promotions.md +307 -0
- package/skills/storefront-best-practices/reference/features/wishlist.md +230 -0
- package/skills/storefront-best-practices/reference/layouts/account.md +380 -0
- package/skills/storefront-best-practices/reference/layouts/cart.md +316 -0
- package/skills/storefront-best-practices/reference/layouts/checkout.md +486 -0
- package/skills/storefront-best-practices/reference/layouts/home-page.md +264 -0
- package/skills/storefront-best-practices/reference/layouts/order-confirmation.md +231 -0
- package/skills/storefront-best-practices/reference/layouts/product-details.md +527 -0
- package/skills/storefront-best-practices/reference/layouts/product-listing.md +520 -0
- package/skills/storefront-best-practices/reference/layouts/static-pages.md +356 -0
- package/skills/storefront-best-practices/reference/medusa.md +307 -0
- package/skills/storefront-best-practices/reference/mobile-responsiveness.md +183 -0
- package/skills/storefront-best-practices/reference/seo.md +195 -0
- package/templates/app/app/[[...slug]]/page.tsx +17 -0
- package/templates/app/app/[[...slug]]/renderer.tsx +10 -0
- package/templates/app/app/globals.css +101 -0
- package/templates/app/app/layout.tsx +35 -0
- package/templates/app/lib/__STOREFRONT__/catalog.ts +132 -0
- package/templates/app/lib/__STOREFRONT__/handlers.ts +33 -0
- package/templates/app/lib/__STOREFRONT__/registry.tsx +134 -0
- package/templates/app/lib/__STOREFRONT__/runtime.ts +25 -0
- package/templates/app/lib/__STOREFRONT__/spec/home.ts +62 -0
- package/templates/app/lib/__STOREFRONT__/spec/index.ts +59 -0
- package/templates/app/lib/__STOREFRONT__/spec/types.ts +14 -0
- package/templates/app/lib/__STOREFRONT__/state.ts +35 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
# Navbar Component
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- [Overview](#overview)
|
|
6
|
+
- [Decision: Simple Dropdown vs Megamenu](#decision-simple-dropdown-vs-megamenu)
|
|
7
|
+
- [Key Ecommerce Patterns](#key-ecommerce-patterns)
|
|
8
|
+
- [Layout Structure](#layout-structure)
|
|
9
|
+
- [Accessibility Essentials](#accessibility-essentials)
|
|
10
|
+
- [Common Ecommerce Mistakes](#common-ecommerce-mistakes)
|
|
11
|
+
- [Backend Integration](#backend-integration)
|
|
12
|
+
- [Checklist](#checklist)
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
Primary navigation for ecommerce storefronts. Desktop: horizontal menu with category links. Mobile: hamburger drawer with accordion subcategories.
|
|
17
|
+
|
|
18
|
+
### ⚠️ CRITICAL: NEVER Hardcode Categories
|
|
19
|
+
|
|
20
|
+
**ALWAYS fetch categories dynamically from the backend. NEVER hardcode static category arrays.**
|
|
21
|
+
|
|
22
|
+
❌ **WRONG - DO NOT DO THIS:**
|
|
23
|
+
```typescript
|
|
24
|
+
// WRONG - Static hardcoded categories
|
|
25
|
+
const categories = [
|
|
26
|
+
{ name: "Women", href: "/categories/women" },
|
|
27
|
+
{ name: "Men", href: "/categories/men" },
|
|
28
|
+
{ name: "Accessories", href: "/categories/accessories" }
|
|
29
|
+
]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
✅ **CORRECT - Fetch from backend:**
|
|
33
|
+
```typescript
|
|
34
|
+
// CORRECT - Fetch categories dynamically
|
|
35
|
+
const [categories, setCategories] = useState([])
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
fetch(`${apiUrl}/store/product-categories`)
|
|
39
|
+
.then(res => res.json())
|
|
40
|
+
.then(data => setCategories(data.product_categories))
|
|
41
|
+
}, [])
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Why this matters:**
|
|
45
|
+
- Categories change frequently (new categories, renamed, reordered)
|
|
46
|
+
- Hardcoded categories become outdated immediately
|
|
47
|
+
- Requires code changes every time categories change
|
|
48
|
+
- Cannot scale to stores with dynamic catalogs
|
|
49
|
+
- Defeats the purpose of headless commerce
|
|
50
|
+
|
|
51
|
+
### Key Requirements
|
|
52
|
+
|
|
53
|
+
- Desktop: Horizontal category links, cart/account/search right-aligned
|
|
54
|
+
- Mobile: Hamburger drawer, cart stays visible in header (not hidden in drawer)
|
|
55
|
+
- **CRITICAL: Fetch categories from backend dynamically (NEVER hardcode static arrays)**
|
|
56
|
+
- Sticky: Recommended for easy cart access while browsing
|
|
57
|
+
- Real-time updates: Cart count, login state, category changes
|
|
58
|
+
|
|
59
|
+
## Decision: Simple Dropdown vs Megamenu
|
|
60
|
+
|
|
61
|
+
**Use Simple Dropdown when:**
|
|
62
|
+
- <10 top-level categories
|
|
63
|
+
- Flat or shallow hierarchy (1-2 levels deep)
|
|
64
|
+
- Minimal subcategories per parent
|
|
65
|
+
- Focused/specialized product catalog
|
|
66
|
+
|
|
67
|
+
**Use Megamenu when:**
|
|
68
|
+
- 10+ top-level categories
|
|
69
|
+
- Deep hierarchy (3+ levels)
|
|
70
|
+
- Need to showcase featured products in navigation
|
|
71
|
+
- Complex product catalog
|
|
72
|
+
- Fashion, electronics, or large inventory
|
|
73
|
+
|
|
74
|
+
**Mobile**: Always use drawer with accordion pattern, never megamenu on mobile.
|
|
75
|
+
|
|
76
|
+
See [megamenu.md](megamenu.md) for megamenu implementation details.
|
|
77
|
+
|
|
78
|
+
## Key Ecommerce Patterns
|
|
79
|
+
|
|
80
|
+
### Cart Indicator (CRITICAL)
|
|
81
|
+
|
|
82
|
+
**Always visible on both desktop and mobile:**
|
|
83
|
+
- Desktop: Top-right, cart icon + count badge
|
|
84
|
+
- Mobile: Top-right in header (NOT hidden in hamburger drawer)
|
|
85
|
+
- This is non-negotiable - users expect cart always accessible
|
|
86
|
+
|
|
87
|
+
**Badge display:**
|
|
88
|
+
- Shows item count (NOT price - confusing when variants change)
|
|
89
|
+
- Only visible when cart has items (count > 0)
|
|
90
|
+
- Show actual count up to 99, then "99+"
|
|
91
|
+
- Position: Top-right corner of cart icon
|
|
92
|
+
- ARIA label: `aria-label="Shopping cart with 3 items"`
|
|
93
|
+
|
|
94
|
+
**Real-time updates:**
|
|
95
|
+
- Update count immediately when items added (optimistic UI)
|
|
96
|
+
- No page refresh required
|
|
97
|
+
- Sync with backend cart state
|
|
98
|
+
- Handle errors gracefully (restore count if add fails)
|
|
99
|
+
|
|
100
|
+
**Click behavior:**
|
|
101
|
+
- Option 1: Navigate to cart page
|
|
102
|
+
- Option 2: Open cart popup/drawer (see cart-popup.md)
|
|
103
|
+
- Choice depends on store type (see cart-popup.md for decision criteria)
|
|
104
|
+
|
|
105
|
+
✅ **CORRECT:**
|
|
106
|
+
- Cart icon visible in mobile header
|
|
107
|
+
- Badge shows count (not price)
|
|
108
|
+
- Updates in real-time without page refresh
|
|
109
|
+
- 44x44px touch target
|
|
110
|
+
- Links to cart or opens cart popup
|
|
111
|
+
|
|
112
|
+
❌ **WRONG:**
|
|
113
|
+
- Hiding cart in mobile hamburger drawer (users can't find it)
|
|
114
|
+
- Showing price in badge (€25.99) instead of count
|
|
115
|
+
- Cart count doesn't update until page refresh
|
|
116
|
+
- No visual feedback when items added
|
|
117
|
+
|
|
118
|
+
### Category Navigation
|
|
119
|
+
|
|
120
|
+
**CRITICAL: Fetch dynamically from backend (NEVER hardcode):**
|
|
121
|
+
|
|
122
|
+
❌ **WRONG - These are all incorrect approaches:**
|
|
123
|
+
```typescript
|
|
124
|
+
// WRONG - Hardcoded array
|
|
125
|
+
const categories = ["Women", "Men", "Kids", "Accessories"]
|
|
126
|
+
|
|
127
|
+
// WRONG - Static object array
|
|
128
|
+
const categories = [
|
|
129
|
+
{ id: 1, name: "Women", slug: "women" },
|
|
130
|
+
{ id: 2, name: "Men", slug: "men" }
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
// WRONG - Importing static data
|
|
134
|
+
import { categories } from "./categories.ts"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
✅ **CORRECT - Fetch from backend API:**
|
|
138
|
+
- Medusa: Use SDK category list method (verify exact method with docs/MCP)
|
|
139
|
+
- Other backends: Call categories endpoint (check API documentation)
|
|
140
|
+
- Fetch on component mount or during server-side rendering
|
|
141
|
+
|
|
142
|
+
**Why dynamic fetching is mandatory:**
|
|
143
|
+
- Store owners add/remove/rename categories frequently
|
|
144
|
+
- Category order and hierarchy changes
|
|
145
|
+
- Multi-language stores need translated category names
|
|
146
|
+
- Featured categories rotate (seasonal, promotions)
|
|
147
|
+
- Hardcoded values require developer intervention for simple changes
|
|
148
|
+
|
|
149
|
+
**Caching strategy:**
|
|
150
|
+
- Cache categories (revalidate on interval or manual trigger)
|
|
151
|
+
- Use SWR, TanStack Query, or framework-level caching
|
|
152
|
+
- Revalidate every 5-10 minutes or on page navigation
|
|
153
|
+
- Update immediately when backend categories change
|
|
154
|
+
|
|
155
|
+
**Organization:**
|
|
156
|
+
- 4-7 top-level categories ideal (max 10 on desktop)
|
|
157
|
+
- Order comes from backend (respects admin's ordering)
|
|
158
|
+
- Keep "Sale" or "New Arrivals" prominent if backend provides it
|
|
159
|
+
- Maximum 2 levels in simple dropdown (category → subcategory)
|
|
160
|
+
- Deeper hierarchies: Use megamenu or separate category pages
|
|
161
|
+
|
|
162
|
+
**Desktop behavior:**
|
|
163
|
+
- Horizontal links with hover dropdowns for subcategories
|
|
164
|
+
- Slight hover delay to prevent accidental triggers
|
|
165
|
+
- Click parent to navigate to category page
|
|
166
|
+
- Click child to navigate to subcategory
|
|
167
|
+
|
|
168
|
+
**Mobile behavior:**
|
|
169
|
+
- All categories in hamburger drawer
|
|
170
|
+
- Accordion pattern for subcategories (expand/collapse)
|
|
171
|
+
- Close drawer on category click (except expanding accordion)
|
|
172
|
+
- Scrollable drawer if categories exceed viewport height
|
|
173
|
+
|
|
174
|
+
✅ **CORRECT:**
|
|
175
|
+
- Categories fetched from backend API on mount
|
|
176
|
+
- Cache with revalidation strategy
|
|
177
|
+
- Respects backend ordering and hierarchy
|
|
178
|
+
- 4-7 top-level items on desktop (based on what backend returns)
|
|
179
|
+
- Accordion for mobile subcategories
|
|
180
|
+
- Consistent ordering across devices
|
|
181
|
+
|
|
182
|
+
❌ **WRONG:**
|
|
183
|
+
- Hardcoded category array in component (NEVER DO THIS)
|
|
184
|
+
- Static categories imported from file (NEVER DO THIS)
|
|
185
|
+
- No cache invalidation (stale categories)
|
|
186
|
+
- Too many top-level items (>10, overwhelming)
|
|
187
|
+
- Different category order on desktop vs mobile
|
|
188
|
+
- Categories don't update when backend changes
|
|
189
|
+
|
|
190
|
+
### User Account Indicator
|
|
191
|
+
|
|
192
|
+
**Two states based on authentication:**
|
|
193
|
+
|
|
194
|
+
**Logged out:**
|
|
195
|
+
- Desktop: "Sign In" or "Log In" text + user icon
|
|
196
|
+
- Mobile: User icon only
|
|
197
|
+
- Click navigates to login page
|
|
198
|
+
- Clear call-to-action
|
|
199
|
+
|
|
200
|
+
**Logged in:**
|
|
201
|
+
- Desktop: User name, initials, or email + dropdown
|
|
202
|
+
- Mobile: User name/initials or icon → account page
|
|
203
|
+
- Dropdown menu (desktop): My Account, Orders, Wishlist, Sign Out
|
|
204
|
+
- Fetch current user from backend authentication state
|
|
205
|
+
|
|
206
|
+
**Authentication state management:**
|
|
207
|
+
- Check auth state from backend (not just localStorage)
|
|
208
|
+
- Update immediately on login/logout events
|
|
209
|
+
- Handle session expiration gracefully
|
|
210
|
+
- Sync across tabs if possible
|
|
211
|
+
|
|
212
|
+
✅ **CORRECT:**
|
|
213
|
+
- Shows "Sign In" when logged out
|
|
214
|
+
- Shows user identifier when logged in
|
|
215
|
+
- Dropdown with account actions
|
|
216
|
+
- Checks backend auth state (not just client state)
|
|
217
|
+
|
|
218
|
+
❌ **WRONG:**
|
|
219
|
+
- No indication of login state
|
|
220
|
+
- Relies solely on localStorage (can be stale)
|
|
221
|
+
- No dropdown for account actions when logged in
|
|
222
|
+
- Missing logout option
|
|
223
|
+
|
|
224
|
+
### Mobile Navigation Pattern
|
|
225
|
+
|
|
226
|
+
**Hamburger drawer:**
|
|
227
|
+
- Trigger: Hamburger icon (top-left)
|
|
228
|
+
- Drawer: Slides from left, 80-85% width, full height, scrollable
|
|
229
|
+
- Backdrop: Semi-transparent overlay, click to close
|
|
230
|
+
- Content: All categories with accordion subcategories
|
|
231
|
+
|
|
232
|
+
**CRITICAL: Keep cart in header:**
|
|
233
|
+
- Cart icon stays in mobile header (top-right)
|
|
234
|
+
- Don't hide cart inside drawer
|
|
235
|
+
- Users expect cart always accessible
|
|
236
|
+
- Same for search icon if using icon-only search
|
|
237
|
+
|
|
238
|
+
**Account in drawer:**
|
|
239
|
+
- Logged out: "Sign In" link in drawer header or top of menu
|
|
240
|
+
- Logged in: User name/initials in drawer header with link to account
|
|
241
|
+
|
|
242
|
+
**Close behavior:**
|
|
243
|
+
- Close button (X) in drawer header
|
|
244
|
+
- Click backdrop overlay
|
|
245
|
+
- Navigate to category (drawer closes)
|
|
246
|
+
- Escape key
|
|
247
|
+
|
|
248
|
+
✅ **CORRECT:**
|
|
249
|
+
- Cart stays in mobile header (visible)
|
|
250
|
+
- Hamburger opens drawer from left
|
|
251
|
+
- Backdrop overlay dims background
|
|
252
|
+
- Close on navigation or backdrop click
|
|
253
|
+
- Scrollable drawer for long menus
|
|
254
|
+
|
|
255
|
+
❌ **WRONG:**
|
|
256
|
+
- Cart hidden inside hamburger drawer (cardinal sin)
|
|
257
|
+
- Full-screen drawer (no backdrop)
|
|
258
|
+
- Drawer doesn't close on navigation
|
|
259
|
+
- Not scrollable (categories cut off)
|
|
260
|
+
|
|
261
|
+
### Bottom Navigation (Alternative for Mobile)
|
|
262
|
+
|
|
263
|
+
**When to use:**
|
|
264
|
+
- Store has 3-5 key sections (Home, Browse, Cart, Account, Search)
|
|
265
|
+
- App-like experience desired
|
|
266
|
+
- Frequent switching between sections
|
|
267
|
+
- Not suitable for complex category hierarchies
|
|
268
|
+
|
|
269
|
+
**Pattern:**
|
|
270
|
+
- Fixed bar at bottom of screen (mobile only)
|
|
271
|
+
- Icon + label for each section
|
|
272
|
+
- Highlight active section
|
|
273
|
+
- 5 items maximum
|
|
274
|
+
- Direct navigation, no dropdowns
|
|
275
|
+
|
|
276
|
+
## Layout Structure
|
|
277
|
+
|
|
278
|
+
**Desktop:**
|
|
279
|
+
- Left: Logo → Homepage
|
|
280
|
+
- Center: Category links (horizontal)
|
|
281
|
+
- Right: Search, Account, Cart
|
|
282
|
+
|
|
283
|
+
**Mobile:**
|
|
284
|
+
- Left: Hamburger
|
|
285
|
+
- Center: Logo
|
|
286
|
+
- Right: Cart (+ Search icon optional)
|
|
287
|
+
|
|
288
|
+
**Sticky recommended:**
|
|
289
|
+
- Keeps cart/account accessible while scrolling
|
|
290
|
+
- Use `position: sticky` or `position: fixed`
|
|
291
|
+
- Solid background color (hide scrolling content)
|
|
292
|
+
- Adequate z-index to stay above content
|
|
293
|
+
|
|
294
|
+
## Accessibility Essentials
|
|
295
|
+
|
|
296
|
+
**Ecommerce-specific ARIA:**
|
|
297
|
+
- Cart count: `aria-live="polite"` to announce changes (e.g., "3 items in cart")
|
|
298
|
+
- Mobile drawer: `role="dialog"`, `aria-modal="true"`
|
|
299
|
+
- Hamburger button: `aria-label="Open navigation menu"`, `aria-expanded="false"`
|
|
300
|
+
- Active page: `aria-current="page"` on current category link
|
|
301
|
+
- Dropdown indicators: `aria-expanded`, `aria-controls` for megamenu relationships
|
|
302
|
+
|
|
303
|
+
**Keyboard navigation:**
|
|
304
|
+
- Tab through all links/buttons
|
|
305
|
+
- Enter/Space to activate
|
|
306
|
+
- Escape to close mobile menu or dropdowns
|
|
307
|
+
- Visible focus indicators (outline/ring)
|
|
308
|
+
|
|
309
|
+
**Generic accessibility applies:**
|
|
310
|
+
- Semantic HTML (`<header>`, `<nav>`)
|
|
311
|
+
- Icon buttons need ARIA labels
|
|
312
|
+
- 4.5:1 color contrast minimum
|
|
313
|
+
- 44x44px touch targets on mobile
|
|
314
|
+
|
|
315
|
+
## Common Ecommerce Mistakes
|
|
316
|
+
|
|
317
|
+
❌ **CRITICAL: Hardcoded static categories** - NEVER create static category arrays like `const categories = ["Women", "Men"]` or import from static files. ALWAYS fetch from backend API. Categories change constantly - new categories added, names changed, ordering updated. Hardcoded categories require developer intervention for simple changes and defeat the purpose of dynamic commerce platforms. This is the #1 most common mistake.
|
|
318
|
+
|
|
319
|
+
❌ **Hiding cart in mobile drawer** - Users expect cart always visible. Keep cart icon in header (top-right), not hidden inside hamburger menu.
|
|
320
|
+
|
|
321
|
+
❌ **No real-time cart updates** - Update count immediately when items added (optimistic UI). Don't require page refresh.
|
|
322
|
+
|
|
323
|
+
❌ **Showing price in cart badge** - Show item count (number), not total price. Price display confuses when variants have different quantities.
|
|
324
|
+
|
|
325
|
+
❌ **No cache invalidation** - Categories become stale when backend changes. Revalidate periodically (5-10 min) or on manual trigger.
|
|
326
|
+
|
|
327
|
+
❌ **Hover-only dropdowns on mobile** - Use click/tap interactions. Hover doesn't work on touch devices.
|
|
328
|
+
|
|
329
|
+
❌ **Desktop navigation on mobile** - Use hamburger drawer pattern, not horizontal menu that doesn't fit.
|
|
330
|
+
|
|
331
|
+
❌ **Inconsistent category order** - Same order on desktop and mobile for consistency. Respect backend's category ordering.
|
|
332
|
+
|
|
333
|
+
## Backend Integration
|
|
334
|
+
|
|
335
|
+
### Category Fetching (CRITICAL - NEVER Hardcode)
|
|
336
|
+
|
|
337
|
+
**Implementation patterns:**
|
|
338
|
+
|
|
339
|
+
**Client-side fetching:**
|
|
340
|
+
- Fetch categories in useEffect on mount
|
|
341
|
+
- Store in state (use appropriate types for Medusa: StoreProductCategory)
|
|
342
|
+
- Handle loading and error states
|
|
343
|
+
- Map categories to navigation links
|
|
344
|
+
- Use category.id as key, category.handle for URL, category.name for display
|
|
345
|
+
|
|
346
|
+
**With caching (RECOMMENDED):**
|
|
347
|
+
- Use TanStack Query with queryKey ['categories']
|
|
348
|
+
- Set staleTime: 5-10 minutes (categories rarely change)
|
|
349
|
+
- Automatic loading/error states
|
|
350
|
+
- Request deduplication if multiple components need categories
|
|
351
|
+
|
|
352
|
+
**Server-side fetching:**
|
|
353
|
+
- Fetch in server component or load function
|
|
354
|
+
- No loading state needed (rendered on server)
|
|
355
|
+
- Better for SEO
|
|
356
|
+
|
|
357
|
+
**Cart state synchronization pattern:**
|
|
358
|
+
- Subscribe to global cart state (Context)
|
|
359
|
+
- Update navbar cart count when cart changes
|
|
360
|
+
- Handle optimistic updates (show new count immediately on add to cart)
|
|
361
|
+
- Sync with backend on events or interval
|
|
362
|
+
|
|
363
|
+
**Authentication state pattern:**
|
|
364
|
+
- Check auth state from backend on mount
|
|
365
|
+
- Listen for login/logout events
|
|
366
|
+
- Update account indicator immediately
|
|
367
|
+
- Handle session expiration gracefully
|
|
368
|
+
|
|
369
|
+
**Category update triggers:**
|
|
370
|
+
- On page load/navigation
|
|
371
|
+
- On manual refresh trigger
|
|
372
|
+
- On revalidation interval (5-10 minutes)
|
|
373
|
+
- After admin updates categories (webhook or polling)
|
|
374
|
+
|
|
375
|
+
## Checklist
|
|
376
|
+
|
|
377
|
+
**Essential navbar features:**
|
|
378
|
+
|
|
379
|
+
- [ ] **CRITICAL: Categories fetched dynamically from backend API (NOT hardcoded arrays)**
|
|
380
|
+
- [ ] **CRITICAL: No static category imports or hardcoded category lists**
|
|
381
|
+
- [ ] Desktop: Horizontal category links
|
|
382
|
+
- [ ] Mobile: Hamburger drawer with accordion
|
|
383
|
+
- [ ] Cart icon visible on both desktop and mobile header (NOT hidden in drawer)
|
|
384
|
+
- [ ] Cart badge shows item count (not price)
|
|
385
|
+
- [ ] Cart count updates in real-time
|
|
386
|
+
- [ ] Categories use backend ordering (not manual ordering)
|
|
387
|
+
- [ ] Account indicator shows login state
|
|
388
|
+
- [ ] Logo links to homepage
|
|
389
|
+
- [ ] 4-7 top-level categories displayed (max 10)
|
|
390
|
+
- [ ] Mobile drawer closes on navigation
|
|
391
|
+
- [ ] Sticky navigation (recommended)
|
|
392
|
+
- [ ] 44x44px minimum touch targets
|
|
393
|
+
- [ ] ARIA labels on icon buttons
|
|
394
|
+
- [ ] `aria-live` on cart count for screen readers
|
|
395
|
+
- [ ] Keyboard accessible with visible focus states
|
|
396
|
+
- [ ] Categories cached with revalidation strategy (5-10 min)
|
|
397
|
+
- [ ] Error handling for failed category fetch
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Popups Component
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- [Overview](#overview)
|
|
6
|
+
- [When to Use Popups](#when-to-use-popups)
|
|
7
|
+
- [Ecommerce Popup Types](#ecommerce-popup-types)
|
|
8
|
+
- [Timing and Triggers](#timing-and-triggers)
|
|
9
|
+
- [Frequency Management](#frequency-management)
|
|
10
|
+
- [Mobile Considerations](#mobile-considerations)
|
|
11
|
+
- [Checklist](#checklist)
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
Popups (modals/overlays) appear over main content to capture attention for specific actions: newsletter signups, promotional offers, exit-intent offers.
|
|
16
|
+
|
|
17
|
+
**Assumed knowledge**: AI agents know how to build modals with close buttons and backdrop overlays. This focuses on ecommerce popup patterns.
|
|
18
|
+
|
|
19
|
+
**Critical balance**: Effective for conversions when used sparingly, intrusive and annoying when overused. Timing and frequency are critical for ecommerce.
|
|
20
|
+
|
|
21
|
+
## When to Use Popups
|
|
22
|
+
|
|
23
|
+
**Use popups when:**
|
|
24
|
+
|
|
25
|
+
- Offering significant value (10-20% first-purchase discount, free shipping)
|
|
26
|
+
- Time-sensitive promotions (flash sale ending soon)
|
|
27
|
+
- Exit-intent to recover abandoning visitors (last chance offer)
|
|
28
|
+
- First-time visitor welcome (one-time only)
|
|
29
|
+
- Important announcements (shipping delays, policy changes)
|
|
30
|
+
|
|
31
|
+
**Don't use popups for:**
|
|
32
|
+
|
|
33
|
+
- Every page visit (extremely annoying)
|
|
34
|
+
- Multiple popups per session
|
|
35
|
+
- Immediate page load (users haven't seen site yet)
|
|
36
|
+
- Mobile users (especially full-screen takeovers - very disruptive)
|
|
37
|
+
- Users who already signed up or dismissed
|
|
38
|
+
|
|
39
|
+
**Consider alternatives:**
|
|
40
|
+
|
|
41
|
+
- Top banner: Less intrusive, always visible, good for ongoing promotions
|
|
42
|
+
- Inline forms: Homepage or footer newsletter signup, non-blocking
|
|
43
|
+
- Slide-in (corner): From bottom-right, less disruptive than center popup
|
|
44
|
+
- Post-purchase: Ask for email after successful order (high conversion)
|
|
45
|
+
|
|
46
|
+
**Popups are best when:** Need immediate attention, high-value offer justifies interruption, exit-intent (last chance).
|
|
47
|
+
|
|
48
|
+
## Ecommerce Popup Types
|
|
49
|
+
|
|
50
|
+
### 1. First-Purchase Discount
|
|
51
|
+
|
|
52
|
+
**Purpose**: Convert first-time visitors with discount incentive.
|
|
53
|
+
|
|
54
|
+
**Content:**
|
|
55
|
+
- Headline: "Welcome! Get 10% Off Your First Order"
|
|
56
|
+
- Email input
|
|
57
|
+
- Discount code or automatic application
|
|
58
|
+
- Subscribe button: "Get My Discount"
|
|
59
|
+
|
|
60
|
+
**Timing**: After 30-60 seconds on site OR after viewing 2-3 products (engagement signal).
|
|
61
|
+
|
|
62
|
+
**Frequency**: Once per user (cookie/localStorage). Don't show to returning customers.
|
|
63
|
+
|
|
64
|
+
### 2. Newsletter Signup
|
|
65
|
+
|
|
66
|
+
**Purpose**: Grow email list for marketing.
|
|
67
|
+
|
|
68
|
+
**Content:**
|
|
69
|
+
- Value proposition: "Get exclusive deals and early access"
|
|
70
|
+
- Email input
|
|
71
|
+
- Subscribe button
|
|
72
|
+
- Optional: First-purchase discount incentive (10-15% off)
|
|
73
|
+
|
|
74
|
+
**Timing**: After 50% scroll OR 60 seconds on site.
|
|
75
|
+
|
|
76
|
+
**Frequency**: Once per session. If dismissed, don't show for 30 days.
|
|
77
|
+
|
|
78
|
+
### 3. Exit-Intent Popup
|
|
79
|
+
|
|
80
|
+
**Purpose**: Recover abandoning visitors with last-chance offer.
|
|
81
|
+
|
|
82
|
+
**Trigger**: Mouse moves toward browser close/back button (desktop only).
|
|
83
|
+
|
|
84
|
+
**Content:**
|
|
85
|
+
- Urgency: "Wait! Don't Miss Out"
|
|
86
|
+
- Offer: "Take 10% Off Your Order" or "Free Shipping Today Only"
|
|
87
|
+
- Email capture (optional): "Send me the code"
|
|
88
|
+
- CTA: "Claim Offer" or "Continue Shopping"
|
|
89
|
+
|
|
90
|
+
**Best for**: Cart abandoners, product page exits, first-time visitors.
|
|
91
|
+
|
|
92
|
+
**Frequency**: Once per session. Don't show if user already added to cart or on checkout.
|
|
93
|
+
|
|
94
|
+
### 4. Cart Abandonment Reminder
|
|
95
|
+
|
|
96
|
+
**Purpose**: Remind user of items in cart before leaving.
|
|
97
|
+
|
|
98
|
+
**Trigger**: Exit-intent when cart has items but user navigating away.
|
|
99
|
+
|
|
100
|
+
**Content:**
|
|
101
|
+
- "Your Cart is Waiting"
|
|
102
|
+
- Show cart summary (items, total)
|
|
103
|
+
- CTA: "Complete Your Order" or "View Cart"
|
|
104
|
+
- Optional incentive: "Complete in 10 minutes for free shipping"
|
|
105
|
+
|
|
106
|
+
**Frequency**: Once per session with items in cart.
|
|
107
|
+
|
|
108
|
+
### 5. Promotional Announcement
|
|
109
|
+
|
|
110
|
+
**Purpose**: Announce sales, new arrivals, or site-wide events.
|
|
111
|
+
|
|
112
|
+
**Content:**
|
|
113
|
+
- Headline: "Flash Sale: 40% Off Everything"
|
|
114
|
+
- Subtext: "Ends in 3 hours"
|
|
115
|
+
- CTA: "Shop Now"
|
|
116
|
+
|
|
117
|
+
**Timing**: Immediate on page load (if major event), OR after 30 seconds.
|
|
118
|
+
|
|
119
|
+
**Frequency**: Once per day during promotion period.
|
|
120
|
+
|
|
121
|
+
## Timing and Triggers
|
|
122
|
+
|
|
123
|
+
**Time-based:**
|
|
124
|
+
- 30-60 seconds after page load (enough time to browse)
|
|
125
|
+
- Never immediate (0 seconds) - users need to see site first
|
|
126
|
+
|
|
127
|
+
**Engagement-based:**
|
|
128
|
+
- After 50% scroll (shows interest)
|
|
129
|
+
- After viewing 2-3 products (qualified visitor)
|
|
130
|
+
- After adding to cart (exit-intent only)
|
|
131
|
+
|
|
132
|
+
**Exit-intent:**
|
|
133
|
+
- Mouse moves toward close/back button (desktop)
|
|
134
|
+
- Scroll-up toward address bar (mobile - less reliable)
|
|
135
|
+
- Only trigger once per session
|
|
136
|
+
- Don't trigger on checkout pages (interrupts purchase)
|
|
137
|
+
|
|
138
|
+
**Page-specific:**
|
|
139
|
+
- Homepage: Welcome/discount popup
|
|
140
|
+
- Product pages: Exit-intent with product-specific offer
|
|
141
|
+
- Cart page: Don't use popups (already engaged)
|
|
142
|
+
- Checkout: Never use popups (critical flow)
|
|
143
|
+
|
|
144
|
+
## Frequency Management
|
|
145
|
+
|
|
146
|
+
**Critical for UX**: Don't show same popup repeatedly to same user.
|
|
147
|
+
|
|
148
|
+
**Implementation:**
|
|
149
|
+
|
|
150
|
+
1. **Cookie/localStorage tracking**: Store dismissal/signup with timestamp
|
|
151
|
+
2. **Respect dismissals**: If user closes popup, don't show for 30 days
|
|
152
|
+
3. **Signed-up users**: Never show newsletter popup again
|
|
153
|
+
4. **Session limits**: Max 1 popup per session
|
|
154
|
+
5. **Time cooldown**: If dismissed, wait 30 days before showing again
|
|
155
|
+
|
|
156
|
+
**Example tracking:**
|
|
157
|
+
```javascript
|
|
158
|
+
// On popup dismiss
|
|
159
|
+
localStorage.setItem('popup_dismissed', Date.now())
|
|
160
|
+
localStorage.setItem('popup_type', 'welcome_discount')
|
|
161
|
+
|
|
162
|
+
// Before showing popup
|
|
163
|
+
const dismissedTime = localStorage.getItem('popup_dismissed')
|
|
164
|
+
const daysSince = (Date.now() - dismissedTime) / (1000 * 60 * 60 * 24)
|
|
165
|
+
if (daysSince < 30) {
|
|
166
|
+
// Don't show popup
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Progressive disclosure:**
|
|
171
|
+
- Session 1: Welcome discount popup
|
|
172
|
+
- Session 2+: Exit-intent only (if applicable)
|
|
173
|
+
- Never stack multiple popups
|
|
174
|
+
|
|
175
|
+
## Mobile Considerations
|
|
176
|
+
|
|
177
|
+
**Mobile popups are MORE intrusive:**
|
|
178
|
+
- Smaller screen = popup takes more space
|
|
179
|
+
- Harder to close (small X button)
|
|
180
|
+
- Disrupts mobile browsing flow
|
|
181
|
+
- Can hurt mobile SEO (Google penalty for intrusive interstitials)
|
|
182
|
+
|
|
183
|
+
**Mobile best practices:**
|
|
184
|
+
|
|
185
|
+
1. **Use sparingly**: Consider top banner or inline forms instead
|
|
186
|
+
2. **Make easily dismissable**: Large close button (44x44px), tap outside to close
|
|
187
|
+
3. **Delay longer**: 60+ seconds instead of 30 seconds
|
|
188
|
+
4. **Smaller size**: 90% width max, not full-screen
|
|
189
|
+
5. **Exit-intent**: Less reliable on mobile, avoid
|
|
190
|
+
6. **Google penalty**: Avoid full-screen popups on mobile (hurts rankings)
|
|
191
|
+
|
|
192
|
+
**Mobile alternative**: Sticky bottom bar (less intrusive)
|
|
193
|
+
- "Get 10% Off - Sign Up" with email input
|
|
194
|
+
- Always visible but doesn't block content
|
|
195
|
+
- Better mobile UX than popup
|
|
196
|
+
|
|
197
|
+
## Checklist
|
|
198
|
+
|
|
199
|
+
**Essential features:**
|
|
200
|
+
|
|
201
|
+
- [ ] Clear value proposition (discount, benefit)
|
|
202
|
+
- [ ] Single focused CTA
|
|
203
|
+
- [ ] Easy to close (X button, backdrop click, Escape key)
|
|
204
|
+
- [ ] Delayed timing (30-60s, not immediate)
|
|
205
|
+
- [ ] Frequency management (localStorage/cookie tracking)
|
|
206
|
+
- [ ] Respect dismissals (30-day cooldown)
|
|
207
|
+
- [ ] Never show to signed-up users
|
|
208
|
+
- [ ] Max 1 popup per session
|
|
209
|
+
- [ ] Exit-intent for cart abandoners (desktop only)
|
|
210
|
+
- [ ] Don't show on checkout pages
|
|
211
|
+
- [ ] Mobile: Use sparingly, consider alternatives
|
|
212
|
+
- [ ] Mobile: Large close button (44x44px)
|
|
213
|
+
- [ ] Mobile: Not full-screen (90% width max)
|
|
214
|
+
- [ ] Email validation before submit
|
|
215
|
+
- [ ] Loading state on submit
|
|
216
|
+
- [ ] Success message or redirect
|
|
217
|
+
- [ ] Keyboard accessible (Tab, Escape, Enter)
|
|
218
|
+
- [ ] `role="dialog"` and `aria-modal="true"`
|
|
219
|
+
- [ ] Focus trap (keep focus within popup)
|
|
220
|
+
- [ ] ARIA label on close button
|
|
221
|
+
- [ ] Screen reader announcements on open
|