From f2080abce538ac715f80b7f5645d5f5b19dcaf85 Mon Sep 17 00:00:00 2001 From: padreug Date: Thu, 25 Sep 2025 22:44:38 +0200 Subject: [PATCH 01/37] Enhance product handling in MerchantStore and useMarket - Enriched products with stall names in MerchantStore, ensuring each product displays the associated stall name or 'Unknown Stall' if not available. - Updated product addition logic in useMarket to include stall names, improving clarity and consistency across the application. - Enhanced error handling for product loading to maintain robust functionality. These changes improve the user experience by providing clearer product information in the market interface. --- .../market/components/MerchantStore.vue | 12 ++++++++- src/modules/market/composables/useMarket.ts | 26 ++++++++++++++----- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/modules/market/components/MerchantStore.vue b/src/modules/market/components/MerchantStore.vue index 854ca9a..6bbcd80 100644 --- a/src/modules/market/components/MerchantStore.vue +++ b/src/modules/market/components/MerchantStore.vue @@ -516,7 +516,17 @@ const loadStallProducts = async () => { inkey, activeStall.value.id! ) - stallProducts.value = products || [] + // Enrich products with stall name + const enrichedProducts = (products || []).map(product => ({ + ...product, + stallName: activeStall.value?.name || 'Unknown Stall' + })) + stallProducts.value = enrichedProducts + + // Also add them to the market store so they appear in the main market + enrichedProducts.forEach(product => { + marketStore.addProduct(product) + }) } catch (error) { console.error('Failed to load products:', error) stallProducts.value = [] diff --git a/src/modules/market/composables/useMarket.ts b/src/modules/market/composables/useMarket.ts index 7365bc8..e4c8828 100644 --- a/src/modules/market/composables/useMarket.ts +++ b/src/modules/market/composables/useMarket.ts @@ -286,13 +286,19 @@ export function useMarket() { productGroups.forEach((productEvents, productId) => { // Sort by created_at and take the most recent const latestEvent = productEvents.sort((a: any, b: any) => b.created_at - a.created_at)[0] - + try { const productData = JSON.parse(latestEvent.content) + const stallId = productData.stall_id || 'unknown' + + // Look up the stall name from the stalls array + const stall = marketStore.stalls.find(s => s.id === stallId) + const stallName = stall?.name || 'Unknown Stall' + const product = { id: productId, - stall_id: productData.stall_id || 'unknown', - stallName: productData.stallName || 'Unknown Stall', + stall_id: stallId, + stallName: stallName, name: productData.name || 'Unnamed Product', description: productData.description || '', price: productData.price || 0, @@ -303,7 +309,7 @@ export function useMarket() { createdAt: latestEvent.created_at, updatedAt: latestEvent.created_at } - + marketStore.addProduct(product) } catch (err) { // Silently handle parse errors @@ -468,10 +474,16 @@ export function useMarket() { const productId = event.tags.find((tag: any) => tag[0] === 'd')?.[1] if (productId) { const productData = JSON.parse(event.content) + const stallId = productData.stall_id || 'unknown' + + // Look up the stall name from the stalls array + const stall = marketStore.stalls.find(s => s.id === stallId) + const stallName = stall?.name || 'Unknown Stall' + const product = { id: productId, - stall_id: productData.stall_id || 'unknown', - stallName: productData.stallName || 'Unknown Stall', + stall_id: stallId, + stallName: stallName, pubkey: event.pubkey, name: productData.name || 'Unnamed Product', description: productData.description || '', @@ -483,7 +495,7 @@ export function useMarket() { createdAt: event.created_at, updatedAt: event.created_at } - + marketStore.addProduct(product) } } catch (err) { From 86d3133978b30c865e4abcbc1f3613690c72813b Mon Sep 17 00:00:00 2001 From: padreug Date: Thu, 25 Sep 2025 22:53:12 +0200 Subject: [PATCH 02/37] Add stall view and product detail dialog in market module - Introduced a new route for viewing individual stalls, allowing users to navigate to a specific stall's page. - Created a ProductDetailDialog component to display detailed information about products, including images, descriptions, and stock status. - Updated MarketPage to handle stall navigation and integrate the new dialog for product details. These enhancements improve the user experience by providing more detailed product information and easier navigation within the market module. --- .../market/components/ProductDetailDialog.vue | 267 +++++++++++++++ src/modules/market/index.ts | 9 + src/modules/market/views/MarketPage.vue | 6 + src/modules/market/views/StallView.vue | 319 ++++++++++++++++++ 4 files changed, 601 insertions(+) create mode 100644 src/modules/market/components/ProductDetailDialog.vue create mode 100644 src/modules/market/views/StallView.vue diff --git a/src/modules/market/components/ProductDetailDialog.vue b/src/modules/market/components/ProductDetailDialog.vue new file mode 100644 index 0000000..e226b37 --- /dev/null +++ b/src/modules/market/components/ProductDetailDialog.vue @@ -0,0 +1,267 @@ + + + + + \ No newline at end of file diff --git a/src/modules/market/index.ts b/src/modules/market/index.ts index 24bf032..93b6bd6 100644 --- a/src/modules/market/index.ts +++ b/src/modules/market/index.ts @@ -145,6 +145,15 @@ export const marketModule: ModulePlugin = { title: 'Checkout', requiresAuth: false } + }, + { + path: '/market/stall/:stallId', + name: 'stall-view', + component: () => import('./views/StallView.vue'), + meta: { + title: 'Stall', + requiresAuth: false + } } ] as RouteRecordRaw[], diff --git a/src/modules/market/views/MarketPage.vue b/src/modules/market/views/MarketPage.vue index 9fa6d47..a52873f 100644 --- a/src/modules/market/views/MarketPage.vue +++ b/src/modules/market/views/MarketPage.vue @@ -81,6 +81,7 @@ :product="product" @add-to-cart="addToCart" @view-details="viewProduct" + @view-stall="viewStall" /> @@ -164,6 +165,11 @@ const viewProduct = (_product: any) => { // TODO: Navigate to product detail page } +const viewStall = (stallId: string) => { + // Navigate to the stall view page + router.push(`/market/stall/${stallId}`) +} + const viewCart = () => { router.push('/cart') } diff --git a/src/modules/market/views/StallView.vue b/src/modules/market/views/StallView.vue new file mode 100644 index 0000000..aca78c8 --- /dev/null +++ b/src/modules/market/views/StallView.vue @@ -0,0 +1,319 @@ + + + + + \ No newline at end of file From 8aa575ffb18f2c6f139f8b44fc6c561c6e46627c Mon Sep 17 00:00:00 2001 From: padreug Date: Thu, 25 Sep 2025 23:02:47 +0200 Subject: [PATCH 03/37] Add MarketFuzzySearch component for enhanced product searching - Introduced a new MarketFuzzySearch component to provide an advanced search interface with keyboard shortcuts, search suggestions, and recent searches functionality. - Updated MarketPage and StallView to integrate the new fuzzy search component, replacing the previous search input implementations. - Enhanced search capabilities with configurable options for better user experience and product discovery. These changes improve the search functionality across the market module, making it easier for users to find products efficiently. --- .../market/components/MarketFuzzySearch.vue | 359 ++++++++++++++++++ src/modules/market/views/MarketPage.vue | 82 +++- src/modules/market/views/StallView.vue | 63 +-- 3 files changed, 470 insertions(+), 34 deletions(-) create mode 100644 src/modules/market/components/MarketFuzzySearch.vue diff --git a/src/modules/market/components/MarketFuzzySearch.vue b/src/modules/market/components/MarketFuzzySearch.vue new file mode 100644 index 0000000..e796533 --- /dev/null +++ b/src/modules/market/components/MarketFuzzySearch.vue @@ -0,0 +1,359 @@ + + + + + \ No newline at end of file diff --git a/src/modules/market/views/MarketPage.vue b/src/modules/market/views/MarketPage.vue index a52873f..564642b 100644 --- a/src/modules/market/views/MarketPage.vue +++ b/src/modules/market/views/MarketPage.vue @@ -40,12 +40,13 @@ - +
-
@@ -68,15 +69,15 @@ -
+

No products found

Try adjusting your search or filters

-
+
+ + \ No newline at end of file diff --git a/src/modules/market/components/CreateProductDialog.vue b/src/modules/market/components/CreateProductDialog.vue index de94285..b1f07c5 100644 --- a/src/modules/market/components/CreateProductDialog.vue +++ b/src/modules/market/components/CreateProductDialog.vue @@ -104,14 +104,19 @@
- + Categories Add categories to help customers find your product -
- -

Category management coming soon

-
+ + +
@@ -202,6 +207,7 @@ import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' import { Checkbox } from '@/components/ui/checkbox' +import CategoryInput from './CategoryInput.vue' import { FormControl, FormDescription, From bb761abe75c63b63c8841e43d31097a06ccfafb6 Mon Sep 17 00:00:00 2001 From: padreug Date: Fri, 26 Sep 2025 00:32:55 +0200 Subject: [PATCH 09/37] Refactor CreateProductDialog and MerchantStore for improved product handling - Updated CreateProductDialog to use `model-value` for Checkbox components, enhancing reactivity and consistency. - Modified MerchantStore to only add active products to the market store, improving product visibility and management. These changes streamline the product creation and management processes, ensuring better user experience and data integrity. --- .../market/components/CreateProductDialog.vue | 15 ++++++++------- src/modules/market/components/MerchantStore.vue | 10 ++++++---- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/modules/market/components/CreateProductDialog.vue b/src/modules/market/components/CreateProductDialog.vue index b1f07c5..7e192d8 100644 --- a/src/modules/market/components/CreateProductDialog.vue +++ b/src/modules/market/components/CreateProductDialog.vue @@ -89,8 +89,9 @@
@@ -141,8 +142,8 @@
@@ -461,13 +462,13 @@ watch(() => props.isOpen, async (isOpen) => { use_autoreply: false, autoreply_message: '' } - + // Reset form with appropriate initial values resetForm({ values: initialValues }) - + // Wait for reactivity await nextTick() - + // Clear any previous errors createError.value = null } diff --git a/src/modules/market/components/MerchantStore.vue b/src/modules/market/components/MerchantStore.vue index 6bbcd80..1438b73 100644 --- a/src/modules/market/components/MerchantStore.vue +++ b/src/modules/market/components/MerchantStore.vue @@ -523,10 +523,12 @@ const loadStallProducts = async () => { })) stallProducts.value = enrichedProducts - // Also add them to the market store so they appear in the main market - enrichedProducts.forEach(product => { - marketStore.addProduct(product) - }) + // Only add active products to the market store so they appear in the main market + enrichedProducts + .filter(product => product.active) + .forEach(product => { + marketStore.addProduct(product) + }) } catch (error) { console.error('Failed to load products:', error) stallProducts.value = [] From 39ecba581ffcdf477f14b183fc70a767673a8b9d Mon Sep 17 00:00:00 2001 From: padreug Date: Fri, 26 Sep 2025 00:03:23 +0200 Subject: [PATCH 10/37] Implement AND/OR filter logic in useCategoryFilter and update MarketPage UI - Added support for AND/OR filtering modes in the `useCategoryFilter` composable, allowing users to filter products based on all or any selected categories. - Introduced reactive state management for filter mode and updated the filtering logic to accommodate the new functionality. - Enhanced the MarketPage UI with a toggle for selecting filter modes, improving user experience and accessibility. - Updated ARIA attributes for better screen reader support in the filter mode selection. These changes significantly enhance the category filtering capabilities, providing users with more control over product visibility. Refactor CreateProductDialog and MarketPage for improved category handling - Updated CreateProductDialog to utilize `model-value` and `@update:model-value` for the CategoryInput component, enhancing reactivity in category selection. - Enhanced MarketPage filtering logic to support AND/OR modes, allowing for more flexible product filtering based on selected categories. - Improved category normalization and matching logic to ensure accurate filtering results. These changes streamline the category management and filtering processes, providing users with a more intuitive experience when creating and finding products. --- .../market/components/CreateProductDialog.vue | 5 +- .../market/composables/useCategoryFilter.ts | 51 +++++++++---- src/modules/market/views/MarketPage.vue | 74 +++++++++++++++++-- 3 files changed, 106 insertions(+), 24 deletions(-) diff --git a/src/modules/market/components/CreateProductDialog.vue b/src/modules/market/components/CreateProductDialog.vue index 7e192d8..636cff7 100644 --- a/src/modules/market/components/CreateProductDialog.vue +++ b/src/modules/market/components/CreateProductDialog.vue @@ -105,13 +105,14 @@
- + Categories Add categories to help customers find your product >(new Set()) + // Filter mode state (reactive) + const filterMode = ref<'any' | 'all'>(options.mode || 'any') + // Computed property for all available categories with counts const allCategories = computed(() => { const categoryMap = new Map() @@ -49,7 +53,7 @@ export function useCategoryFilter( .sort((a, b) => b.count - a.count) // Sort by popularity }) - // Optimized product filtering with early returns and efficient lookups + // Optimized product filtering with AND/OR logic const filteredProducts = computed(() => { const selectedSet = selectedCategories.value @@ -58,26 +62,34 @@ export function useCategoryFilter( return products.value } - // Convert selected categories to array for faster iteration in some cases - const selectedArray = Array.from(selectedSet) - return products.value.filter(product => { // Handle empty categories if (!product.categories?.length) { return options.includeEmpty || false } - // Check if product has any selected category (optimized) - for (const cat of product.categories) { - if (cat && cat.trim()) { - const normalizedCategory = options.caseSensitive ? cat : cat.toLowerCase() - if (selectedSet.has(normalizedCategory)) { - return true // Early return on first match - } - } + // Normalize product categories + const productCategories = product.categories + .filter(cat => cat && cat.trim()) + .map(cat => options.caseSensitive ? cat : cat.toLowerCase()) + + if (productCategories.length === 0) { + return options.includeEmpty || false } - return false + // Count matches between product categories and selected categories + const matchingCategories = productCategories.filter(cat => + selectedSet.has(cat) + ) + + // Apply AND/OR logic + if (filterMode.value === 'all') { + // AND logic: Product must have ALL selected categories + return matchingCategories.length === selectedSet.size + } else { + // OR logic: Product must have ANY selected category + return matchingCategories.length > 0 + } }) }) @@ -139,6 +151,14 @@ export function useCategoryFilter( return selectedCategories.value.has(normalizedCategory) } + const setFilterMode = (mode: 'any' | 'all') => { + filterMode.value = mode + } + + const toggleFilterMode = () => { + filterMode.value = filterMode.value === 'any' ? 'all' : 'any' + } + // Category statistics const categoryStats = computed(() => ({ totalCategories: allCategories.value.length, @@ -150,6 +170,7 @@ export function useCategoryFilter( return { // State (readonly to prevent external mutation) selectedCategories: readonly(selectedCategories), + filterMode: readonly(filterMode), allCategories, filteredProducts, selectedCount, @@ -163,7 +184,9 @@ export function useCategoryFilter( removeCategory, clearAllCategories, selectMultipleCategories, - isSelected + isSelected, + setFilterMode, + toggleFilterMode } } diff --git a/src/modules/market/views/MarketPage.vue b/src/modules/market/views/MarketPage.vue index 868cf5d..1c972cc 100644 --- a/src/modules/market/views/MarketPage.vue +++ b/src/modules/market/views/MarketPage.vue @@ -61,9 +61,42 @@ aria-labelledby="category-filters-heading" >
-

- Browse by Category -

+
+

+ Browse by Category +

+ + +
+ Match: + + +
+
+ + +
+ +
+
Try searching for:
+
+ +
-
- -
-
-
Recent searches:
- -
-
- + +
+
+
Recent searches:
+ +
+
+ +
@@ -267,7 +275,14 @@ const handleClear = () => { emit('search', '') emit('results', filteredItems.value) emit('clear') - searchInputRef.value?.focus() + + // Focus the input after clearing + if (searchInputRef.value) { + const inputElement = searchInputRef.value.$el?.querySelector('input') || searchInputRef.value.$el + if (inputElement && typeof inputElement.focus === 'function') { + inputElement.focus() + } + } } const handleKeydown = (event: KeyboardEvent) => { @@ -275,8 +290,11 @@ const handleKeydown = (event: KeyboardEvent) => { if (event.key === 'Escape') { if (searchQuery.value) { handleClear() - } else { - searchInputRef.value?.blur() + } else if (searchInputRef.value) { + const inputElement = searchInputRef.value.$el?.querySelector('input') || searchInputRef.value.$el + if (inputElement && typeof inputElement.blur === 'function') { + inputElement.blur() + } } event.preventDefault() } @@ -323,7 +341,14 @@ const handleGlobalKeydown = (event: KeyboardEvent) => { // ⌘K or Ctrl+K to focus search if ((event.metaKey || event.ctrlKey) && event.key === 'k') { event.preventDefault() - searchInputRef.value?.focus() + console.log('⌘K/Ctrl+K pressed, focusing search input', !!searchInputRef.value) + if (searchInputRef.value) { + // 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() + } + } } } @@ -335,20 +360,10 @@ watch(filteredItems, (items) => { // Setup and cleanup onMounted(() => { document.addEventListener('keydown', handleGlobalKeydown) - - if (searchInputRef.value) { - searchInputRef.value.addEventListener('focus', handleFocus) - searchInputRef.value.addEventListener('blur', handleBlur) - } }) onUnmounted(() => { document.removeEventListener('keydown', handleGlobalKeydown) - - if (searchInputRef.value) { - searchInputRef.value.removeEventListener('focus', handleFocus) - searchInputRef.value.removeEventListener('blur', handleBlur) - } }) From 8fe53d3d7166a7815988552a7b6fe39f21236b25 Mon Sep 17 00:00:00 2001 From: padreug Date: Fri, 26 Sep 2025 10:33:49 +0200 Subject: [PATCH 14/37] Refactor MarketPage layout for improved mobile responsiveness and user experience - Optimized the Market Header and Fuzzy Search components for better mobile display, enhancing usability on smaller screens. - Adjusted spacing and font sizes for various elements to ensure a cohesive look across devices. - Improved the active filters summary and category filters for better accessibility and visual clarity. These changes enhance the overall user experience by providing a more responsive and visually appealing interface in the MarketPage. --- src/modules/market/views/MarketPage.vue | 106 ++++++++++++------------ 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/src/modules/market/views/MarketPage.vue b/src/modules/market/views/MarketPage.vue index b5d55f7..76540e6 100644 --- a/src/modules/market/views/MarketPage.vue +++ b/src/modules/market/views/MarketPage.vue @@ -23,32 +23,36 @@
- -
-
- - - M - -
-

- {{ marketStore.activeMarket?.opts?.name || 'Market' }} -

-

- {{ marketStore.activeMarket.opts.description }} -

+ +
+ +
+ +
+ + + M + +
+

+ {{ marketStore.activeMarket?.opts?.name || 'Market' }} +

+

+ {{ marketStore.activeMarket.opts.description }} +

+
+
+ + +
+
-
- - -
-
@@ -57,28 +61,28 @@
-
-
-

+
+
+

Browse by Category

- Match: +
@@ -130,7 +134,7 @@ > -
+
{{ category.category }}
- +
@@ -169,34 +173,34 @@
-
-
-

From a82360b283f17de2dc72dacc1f30e07d285f1e8f Mon Sep 17 00:00:00 2001 From: padreug Date: Fri, 26 Sep 2025 10:40:58 +0200 Subject: [PATCH 16/37] Enhance MarketFuzzySearch component with responsive keyboard hints - Updated the keyboard shortcuts hint to display only on desktop devices, improving usability for users on larger screens. - Introduced responsive breakpoints to manage the visibility of the enhanced placeholder, ensuring a better user experience across different device sizes. These changes refine the search component by tailoring the interface to the user's device, enhancing accessibility and interaction. --- src/modules/market/components/MarketFuzzySearch.vue | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/modules/market/components/MarketFuzzySearch.vue b/src/modules/market/components/MarketFuzzySearch.vue index ec367f7..5054398 100644 --- a/src/modules/market/components/MarketFuzzySearch.vue +++ b/src/modules/market/components/MarketFuzzySearch.vue @@ -20,7 +20,7 @@
-