feat: add CartButton component for consistent cart access across views
- Introduced a new CartButton component to encapsulate cart summary functionality, improving code reusability and maintainability. - Updated MarketPage.vue and StallView.vue to utilize the CartButton component, enhancing user navigation to the cart. - Removed redundant cart summary code from both views, streamlining the component structure. These changes provide a unified and consistent user experience for accessing the cart across different market views.
This commit is contained in:
parent
688bf5e105
commit
da5c4d6de1
5 changed files with 515 additions and 24 deletions
23
src/modules/market/components/CartButton.vue
Normal file
23
src/modules/market/components/CartButton.vue
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<template>
|
||||||
|
<!-- Cart Summary Button -->
|
||||||
|
<div v-if="marketStore.totalCartItems > 0" class="fixed bottom-4 right-4 z-50">
|
||||||
|
<Button @click="viewCart" class="shadow-lg">
|
||||||
|
<ShoppingCart class="w-5 h-5 mr-2" />
|
||||||
|
Cart ({{ marketStore.totalCartItems }})
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useMarketStore } from '@/modules/market/stores/market'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { ShoppingCart } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const marketStore = useMarketStore()
|
||||||
|
|
||||||
|
const viewCart = () => {
|
||||||
|
router.push('/cart')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
53
src/modules/market/composables/useSearchKeyboardShortcuts.ts
Normal file
53
src/modules/market/composables/useSearchKeyboardShortcuts.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { onMounted, onUnmounted, type Ref } from 'vue'
|
||||||
|
|
||||||
|
export function useSearchKeyboardShortcuts(searchInputRef: Ref<any>) {
|
||||||
|
const focusSearchInput = () => {
|
||||||
|
if (searchInputRef.value?.$el) {
|
||||||
|
// Access the underlying HTML input element from the Shadcn Input component
|
||||||
|
const inputElement = searchInputRef.value.$el.querySelector('input') || searchInputRef.value.$el
|
||||||
|
if (inputElement && typeof inputElement.focus === 'function') {
|
||||||
|
inputElement.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const blurSearchInput = () => {
|
||||||
|
if (searchInputRef.value?.$el) {
|
||||||
|
const inputElement = searchInputRef.value.$el.querySelector('input') || searchInputRef.value.$el
|
||||||
|
if (inputElement && typeof inputElement.blur === 'function') {
|
||||||
|
inputElement.blur()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGlobalKeydown = (event: KeyboardEvent) => {
|
||||||
|
// ⌘K or Ctrl+K to focus search
|
||||||
|
if ((event.metaKey || event.ctrlKey) && event.key === 'k') {
|
||||||
|
event.preventDefault()
|
||||||
|
focusSearchInput()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSearchKeydown = (event: KeyboardEvent) => {
|
||||||
|
// Escape key clears search or blurs input
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
event.preventDefault()
|
||||||
|
return true // Signal to clear search
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('keydown', handleGlobalKeydown)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('keydown', handleGlobalKeydown)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
focusSearchInput,
|
||||||
|
blurSearchInput,
|
||||||
|
handleSearchKeydown
|
||||||
|
}
|
||||||
|
}
|
||||||
430
src/modules/market/docs/category-filter-improvements.md
Normal file
430
src/modules/market/docs/category-filter-improvements.md
Normal file
|
|
@ -0,0 +1,430 @@
|
||||||
|
# Category Filter System - Future Improvements
|
||||||
|
|
||||||
|
This document outlines potential enhancements to the category filtering system based on user needs and advanced UX patterns.
|
||||||
|
|
||||||
|
## 🎯 Current Implementation Status
|
||||||
|
|
||||||
|
✅ **Completed:**
|
||||||
|
- Reusable `useCategoryFilter` composable
|
||||||
|
- Set-based performance optimizations
|
||||||
|
- Full accessibility (ARIA, keyboard navigation, screen readers)
|
||||||
|
- Theme-aware semantic styling
|
||||||
|
- Proper Nostr event tag extraction (`'t'` tags)
|
||||||
|
- Real-time reactive filtering
|
||||||
|
|
||||||
|
## 🚀 Proposed Future Improvements
|
||||||
|
|
||||||
|
### 1. **Advanced Filtering Logic**
|
||||||
|
|
||||||
|
#### ~~AND/OR Filter Modes~~
|
||||||
|
✅ impemented!
|
||||||
|
|
||||||
|
Currently uses OR logic (show products with ANY selected category). Add support for AND logic.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface AdvancedFilterOptions {
|
||||||
|
mode: 'any' | 'all' // OR vs AND logic
|
||||||
|
caseSensitive: boolean
|
||||||
|
includeEmpty: boolean
|
||||||
|
minCount: number
|
||||||
|
maxSelections?: number // Limit concurrent selections
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- More precise filtering for power users
|
||||||
|
- Better product discovery in large catalogs
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
```typescript
|
||||||
|
// In useCategoryFilter.ts
|
||||||
|
const filteredProducts = computed(() => {
|
||||||
|
// ... existing code
|
||||||
|
|
||||||
|
return products.value.filter(product => {
|
||||||
|
const matches = product.categories?.filter(cat =>
|
||||||
|
selectedCategories.value.has(cat.toLowerCase())
|
||||||
|
) || []
|
||||||
|
|
||||||
|
return options.mode === 'any'
|
||||||
|
? matches.length > 0
|
||||||
|
: matches.length === selectedCategories.value.size
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. **Hierarchical Categories**
|
||||||
|
|
||||||
|
Support nested category structures for better organization.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface HierarchicalCategory {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
parent?: string
|
||||||
|
children?: string[]
|
||||||
|
level: number
|
||||||
|
path: string[] // e.g., ['Electronics', 'Computers', 'Laptops']
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**UI Enhancement:**
|
||||||
|
- Expandable tree structure
|
||||||
|
- Breadcrumb navigation
|
||||||
|
- Parent/child selection logic
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```
|
||||||
|
📁 Electronics (25)
|
||||||
|
└── 💻 Computers (12)
|
||||||
|
└── 💾 Storage (5)
|
||||||
|
└── 📱 Mobile (13)
|
||||||
|
📁 Clothing (18)
|
||||||
|
└── 👕 Shirts (8)
|
||||||
|
└── 👖 Pants (10)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. **Search Within Categories**
|
||||||
|
|
||||||
|
Add search functionality for large category lists.
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div class="category-search mb-3">
|
||||||
|
<Input
|
||||||
|
v-model="categorySearchQuery"
|
||||||
|
placeholder="Search categories..."
|
||||||
|
class="text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="category-list max-h-64 overflow-y-auto">
|
||||||
|
<div v-for="category in filteredCategories" ...>
|
||||||
|
<!-- Highlight matching text -->
|
||||||
|
<span v-html="highlightMatch(category.name, categorySearchQuery)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Fuzzy search within category names
|
||||||
|
- Highlight matching text
|
||||||
|
- Keyboard navigation through results
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. **Category Metadata & Visualization**
|
||||||
|
|
||||||
|
Enhance categories with rich metadata and visual cues.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface EnhancedCategoryItem {
|
||||||
|
category: string
|
||||||
|
count: number
|
||||||
|
selected: boolean
|
||||||
|
metadata?: {
|
||||||
|
color?: string // Brand color for visual consistency
|
||||||
|
icon?: string // Lucide icon name or emoji
|
||||||
|
description?: string // Tooltip description
|
||||||
|
trending?: boolean // Popular/trending indicator
|
||||||
|
new?: boolean // Recently added categories
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Visual Enhancements:**
|
||||||
|
```vue
|
||||||
|
<Badge :style="{ backgroundColor: category.metadata?.color }">
|
||||||
|
<component :is="category.metadata?.icon" class="w-3 h-3 mr-1" />
|
||||||
|
{{ category.category }}
|
||||||
|
<TrendingUp v-if="category.metadata?.trending" class="w-3 h-3 ml-1" />
|
||||||
|
</Badge>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. **Persistent Filter State**
|
||||||
|
|
||||||
|
Remember user preferences across sessions.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// composables/usePersistentCategoryFilter.ts
|
||||||
|
export function usePersistentCategoryFilter() {
|
||||||
|
const storageKey = 'market-category-filters'
|
||||||
|
|
||||||
|
const savedFilters = useLocalStorage(storageKey, {
|
||||||
|
selectedCategories: [] as string[],
|
||||||
|
filterMode: 'any' as 'any' | 'all',
|
||||||
|
sortPreference: 'popularity' as 'popularity' | 'alphabetical'
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
savedFilters,
|
||||||
|
saveCurrentState,
|
||||||
|
restoreState,
|
||||||
|
clearSavedState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Remember selected categories
|
||||||
|
- Save filter preferences (AND/OR mode)
|
||||||
|
- Cross-device sync (if user is authenticated)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. **Smart Categories & Auto-suggestions**
|
||||||
|
|
||||||
|
AI-powered category suggestions and smart filtering.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface SmartCategoryFeatures {
|
||||||
|
suggestCategories: (searchQuery: string) => string[]
|
||||||
|
relatedCategories: (selectedCategory: string) => string[]
|
||||||
|
popularCombinations: () => string[][]
|
||||||
|
seasonalRecommendations: () => string[]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementation Ideas:**
|
||||||
|
- "Users who selected X also selected Y"
|
||||||
|
- Seasonal category promotion (winter → clothing, electronics)
|
||||||
|
- Search query to category mapping
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. **Advanced UI Patterns**
|
||||||
|
|
||||||
|
#### Multi-Column Layout
|
||||||
|
For markets with many categories:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2">
|
||||||
|
<CategoryColumn
|
||||||
|
v-for="column in categorizedColumns"
|
||||||
|
:categories="column"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Collapsible Groups
|
||||||
|
Group categories by type with expand/collapse:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<details v-for="group in categoryGroups" class="border rounded mb-2">
|
||||||
|
<summary class="font-semibold p-2 cursor-pointer">
|
||||||
|
{{ group.name }} ({{ group.totalCount }})
|
||||||
|
</summary>
|
||||||
|
<div class="p-2 pt-0">
|
||||||
|
<CategoryBadge v-for="cat in group.categories" ... />
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Tag Cloud Visualization
|
||||||
|
Show categories sized by popularity:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<div class="tag-cloud">
|
||||||
|
<button
|
||||||
|
v-for="category in allCategories"
|
||||||
|
:style="{ fontSize: getTagSize(category.count) }"
|
||||||
|
class="tag-item"
|
||||||
|
>
|
||||||
|
{{ category.category }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. **Performance Optimizations**
|
||||||
|
|
||||||
|
#### Virtual Scrolling
|
||||||
|
For markets with 100+ categories:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
import { RecycleScroller } from 'vue-virtual-scroller'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RecycleScroller
|
||||||
|
class="category-scroller"
|
||||||
|
:items="allCategories"
|
||||||
|
:item-size="40"
|
||||||
|
key-field="category"
|
||||||
|
v-slot="{ item }"
|
||||||
|
>
|
||||||
|
<CategoryBadge :category="item" />
|
||||||
|
</RecycleScroller>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Web Workers
|
||||||
|
For heavy category processing:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// workers/categoryProcessor.ts
|
||||||
|
self.onmessage = function(e) {
|
||||||
|
const { products, options } = e.data
|
||||||
|
const categoryMap = processCategoriesInWorker(products, options)
|
||||||
|
self.postMessage(categoryMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// In composable
|
||||||
|
const categoryWorker = new Worker('/workers/categoryProcessor.js')
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. **Analytics & Insights**
|
||||||
|
|
||||||
|
Track category usage for business intelligence:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface CategoryAnalytics {
|
||||||
|
trackCategorySelection: (category: string) => void
|
||||||
|
trackFilterCombination: (categories: string[]) => void
|
||||||
|
trackSearchPatterns: (query: string, results: number) => void
|
||||||
|
generateInsights: () => {
|
||||||
|
popularCategories: string[]
|
||||||
|
unusedCategories: string[]
|
||||||
|
conversionByCategory: Record<string, number>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 10. **Mobile-First Enhancements**
|
||||||
|
|
||||||
|
#### Bottom Sheet Interface
|
||||||
|
Mobile-optimized category selector:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<Sheet v-model:open="showCategorySheet">
|
||||||
|
<SheetContent side="bottom" class="h-[70vh]">
|
||||||
|
<SheetHeader>
|
||||||
|
<SheetTitle>Filter by Category</SheetTitle>
|
||||||
|
</SheetHeader>
|
||||||
|
<ScrollArea class="flex-1">
|
||||||
|
<CategoryGrid :categories="allCategories" />
|
||||||
|
</ScrollArea>
|
||||||
|
<SheetFooter>
|
||||||
|
<Button @click="applyFilters">Apply Filters</Button>
|
||||||
|
</SheetFooter>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Swipe Gestures
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
import { useSwipe } from '@vueuse/core'
|
||||||
|
|
||||||
|
const { isSwiping, direction } = useSwipe(categoryContainer, {
|
||||||
|
onSwipeEnd(e, direction) {
|
||||||
|
if (direction === 'left') nextCategoryPage()
|
||||||
|
if (direction === 'right') prevCategoryPage()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Implementation Priority
|
||||||
|
|
||||||
|
### **Phase 1: Essential UX** (2-3 days)
|
||||||
|
1. ✅ AND/OR filter modes
|
||||||
|
2. ✅ Persistent filter state
|
||||||
|
3. ✅ Mobile bottom sheet interface
|
||||||
|
|
||||||
|
### **Phase 2: Advanced Features** (1-2 weeks)
|
||||||
|
1. 🔄 Hierarchical categories
|
||||||
|
2. 🔄 Category search functionality
|
||||||
|
3. 🔄 Smart suggestions
|
||||||
|
|
||||||
|
### **Phase 3: Enterprise Features** (2-3 weeks)
|
||||||
|
1. 🔄 Analytics & insights
|
||||||
|
2. 🔄 Virtual scrolling
|
||||||
|
3. 🔄 Web worker optimizations
|
||||||
|
|
||||||
|
### **Phase 4: Polish** (1 week)
|
||||||
|
1. 🔄 Enhanced visualizations
|
||||||
|
2. 🔄 Advanced animations
|
||||||
|
3. 🔄 A11y improvements
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Testing Strategy
|
||||||
|
|
||||||
|
### **Unit Tests**
|
||||||
|
```typescript
|
||||||
|
// tests/useCategoryFilter.test.ts
|
||||||
|
describe('useCategoryFilter', () => {
|
||||||
|
test('should handle AND/OR filter modes', () => {
|
||||||
|
// Test implementation
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should persist selected categories', () => {
|
||||||
|
// Test localStorage integration
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### **E2E Tests**
|
||||||
|
```typescript
|
||||||
|
// e2e/category-filtering.spec.ts
|
||||||
|
test('category filtering workflow', async ({ page }) => {
|
||||||
|
await page.goto('/market')
|
||||||
|
|
||||||
|
// Test category selection
|
||||||
|
await page.click('[data-testid="category-electronics"]')
|
||||||
|
await expect(page.locator('[data-testid="product-grid"]')).toContainText('Electronics')
|
||||||
|
|
||||||
|
// Test filter persistence
|
||||||
|
await page.reload()
|
||||||
|
await expect(page.locator('[data-testid="category-electronics"]')).toHaveClass(/selected/)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Success Metrics
|
||||||
|
|
||||||
|
### **Performance Metrics**
|
||||||
|
- Category rendering time < 100ms
|
||||||
|
- Filter application time < 50ms
|
||||||
|
- Memory usage < 10MB for 1000+ categories
|
||||||
|
|
||||||
|
### **UX Metrics**
|
||||||
|
- Category selection rate > 60%
|
||||||
|
- Filter abandonment rate < 10%
|
||||||
|
- Mobile usability score > 95%
|
||||||
|
|
||||||
|
### **Business Metrics**
|
||||||
|
- Product discovery improvement
|
||||||
|
- Conversion rate by category
|
||||||
|
- User engagement with filtering features
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 Related Documentation
|
||||||
|
|
||||||
|
- [Vue 3 Composition API Guide](https://vuejs.org/guide/extras/composition-api-faq.html)
|
||||||
|
- [VueUse Composables](https://vueuse.org/)
|
||||||
|
- [Accessibility Guidelines (WCAG 2.1)](https://www.w3.org/WAI/WCAG21/quickref/)
|
||||||
|
- [Nostr NIP Standards](https://github.com/nostr-protocol/nips)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: $(date +%Y-%m-%d)*
|
||||||
|
*Next review: 2024-02-01*
|
||||||
|
|
@ -81,15 +81,11 @@
|
||||||
@view-stall="viewStall"
|
@view-stall="viewStall"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Cart Summary -->
|
|
||||||
<div v-if="marketStore.totalCartItems > 0" class="fixed bottom-4 right-4">
|
|
||||||
<Button @click="viewCart" class="shadow-lg">
|
|
||||||
<ShoppingCart class="w-5 h-5 mr-2" />
|
|
||||||
Cart ({{ marketStore.totalCartItems }})
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Cart Summary -->
|
||||||
|
<CartButton />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -103,10 +99,10 @@ import { useCategoryFilter } from '../composables/useCategoryFilter'
|
||||||
import { config } from '@/lib/config'
|
import { config } from '@/lib/config'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'
|
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'
|
||||||
import { ShoppingCart } from 'lucide-vue-next'
|
|
||||||
import MarketFuzzySearch from '../components/MarketFuzzySearch.vue'
|
import MarketFuzzySearch from '../components/MarketFuzzySearch.vue'
|
||||||
import ProductGrid from '../components/ProductGrid.vue'
|
import ProductGrid from '../components/ProductGrid.vue'
|
||||||
import CategoryFilterBar from '../components/CategoryFilterBar.vue'
|
import CategoryFilterBar from '../components/CategoryFilterBar.vue'
|
||||||
|
import CartButton from '../components/CartButton.vue'
|
||||||
import type { Product } from '../types/market'
|
import type { Product } from '../types/market'
|
||||||
import type { FuzzySearchOptions } from '@/composables/useFuzzySearch'
|
import type { FuzzySearchOptions } from '@/composables/useFuzzySearch'
|
||||||
|
|
||||||
|
|
@ -249,10 +245,6 @@ const viewStall = (stallId: string) => {
|
||||||
router.push(`/market/stall/${stallId}`)
|
router.push(`/market/stall/${stallId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewCart = () => {
|
|
||||||
router.push('/cart')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle fuzzy search results
|
// Handle fuzzy search results
|
||||||
const handleSearchResults = (results: Product[]) => {
|
const handleSearchResults = (results: Product[]) => {
|
||||||
searchResults.value = results
|
searchResults.value = results
|
||||||
|
|
|
||||||
|
|
@ -130,14 +130,10 @@
|
||||||
@add-to-cart="handleAddToCart"
|
@add-to-cart="handleAddToCart"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Cart Summary -->
|
|
||||||
<div v-if="marketStore.totalCartItems > 0" class="fixed bottom-4 right-4">
|
|
||||||
<Button @click="viewCart" class="shadow-lg">
|
|
||||||
<ShoppingCart class="w-5 h-5 mr-2" />
|
|
||||||
Cart ({{ marketStore.totalCartItems }})
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Cart Summary -->
|
||||||
|
<CartButton />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
@ -154,9 +150,10 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select'
|
} from '@/components/ui/select'
|
||||||
import { ArrowLeft, Store, X, ShoppingCart } from 'lucide-vue-next'
|
import { ArrowLeft, Store, X } from 'lucide-vue-next'
|
||||||
import FuzzySearch from '@/components/ui/fuzzy-search/FuzzySearch.vue'
|
import FuzzySearch from '@/components/ui/fuzzy-search/FuzzySearch.vue'
|
||||||
import ProductGrid from '../components/ProductGrid.vue'
|
import ProductGrid from '../components/ProductGrid.vue'
|
||||||
|
import CartButton from '../components/CartButton.vue'
|
||||||
import type { Product, Stall } from '../types/market'
|
import type { Product, Stall } from '../types/market'
|
||||||
import type { FuzzySearchOptions } from '@/composables/useFuzzySearch'
|
import type { FuzzySearchOptions } from '@/composables/useFuzzySearch'
|
||||||
|
|
||||||
|
|
@ -280,10 +277,6 @@ const handleAddToCart = (product: Product, quantity?: number) => {
|
||||||
marketStore.addToStallCart(product, quantity || 1)
|
marketStore.addToStallCart(product, quantity || 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewCart = () => {
|
|
||||||
router.push('/cart')
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const viewStall = (otherStallId: string) => {
|
const viewStall = (otherStallId: string) => {
|
||||||
if (otherStallId !== stallId.value) {
|
if (otherStallId !== stallId.value) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue