Enhance MerchantStore component with improved stall management and loading states

- Refactor the MerchantStore component to display a grid of user stalls, including loading indicators and action buttons for managing stalls and viewing products.
- Introduce a new card for creating additional stores, enhancing the user experience for merchants.
- Update the NostrmarketAPI to correct the endpoint for fetching stalls, ensuring accurate data retrieval.
- Implement state management for user stalls and active stall selection, improving the overall functionality and responsiveness of the component.

These changes streamline the stall management process for merchants, providing a more intuitive interface and better feedback during interactions.
This commit is contained in:
padreug 2025-09-08 17:46:54 +02:00
parent e6107839a0
commit 6f68c2320e
2 changed files with 162 additions and 36 deletions

View file

@ -47,28 +47,112 @@
</Button>
</div>
<!-- No Stalls Empty State (has merchant profile but no stalls) -->
<div v-else-if="!userHasStalls" class="flex flex-col items-center justify-center py-12">
<div class="w-24 h-24 bg-muted rounded-full flex items-center justify-center mb-6">
<Store class="w-12 h-12 text-muted-foreground" />
</div>
<h2 class="text-2xl font-bold text-foreground mb-2">Create Your First Store</h2>
<p class="text-muted-foreground text-center mb-6 max-w-md">
Great! You have a merchant profile. Now create your first store (stall) to start listing products and receiving orders.
</p>
<Button @click="initializeStallCreation" variant="default" size="lg">
<Plus class="w-5 h-5 mr-2" />
Create Store
</Button>
</div>
<!-- Store Content (shown when user has a store) -->
<!-- Stores Grid (shown when merchant profile exists) -->
<div v-else>
<!-- Header Section -->
<div class="mb-8">
<div class="flex items-center justify-between mb-2">
<div>
<h2 class="text-2xl font-bold text-foreground">My Stores</h2>
<p class="text-muted-foreground mt-1">
Manage your stores and products
</p>
</div>
<Button @click="navigateToMarket" variant="outline">
<Store class="w-4 h-4 mr-2" />
Browse Market
</Button>
</div>
</div>
<!-- Loading State for Stalls -->
<div v-if="isLoadingStalls" class="flex justify-center py-12">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
<!-- Stores Cards Grid -->
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
<!-- Existing Store Cards -->
<div v-for="stall in userStalls" :key="stall.id"
class="bg-card rounded-lg border shadow-sm hover:shadow-md transition-shadow">
<div class="p-6">
<div class="flex items-start justify-between mb-4">
<div class="flex-1">
<h3 class="text-lg font-semibold text-foreground">{{ stall.name }}</h3>
<p class="text-sm text-muted-foreground mt-1">
{{ stall.config?.description || 'No description' }}
</p>
</div>
<Badge variant="secondary">{{ stall.currency }}</Badge>
</div>
<div class="space-y-3">
<!-- Store Metrics -->
<div class="flex items-center justify-between text-sm">
<span class="text-muted-foreground">Products</span>
<span class="font-medium">{{ stall.products?.length || 0 }}</span>
</div>
<div class="flex items-center justify-between text-sm">
<span class="text-muted-foreground">Shipping Zones</span>
<span class="font-medium">{{ stall.shipping_zones?.length || 0 }}</span>
</div>
<!-- Action Buttons -->
<div class="flex gap-2 pt-3">
<Button
@click="manageStall(stall.id)"
variant="default"
size="sm"
class="flex-1"
>
<Settings class="w-4 h-4 mr-1" />
Manage
</Button>
<Button
@click="viewStallProducts(stall.id)"
variant="outline"
size="sm"
class="flex-1"
>
<Package class="w-4 h-4 mr-1" />
Products
</Button>
</div>
</div>
</div>
</div>
<!-- Create New Store Card -->
<div class="bg-card rounded-lg border-2 border-dashed border-muted-foreground/25 hover:border-primary/50 transition-colors">
<button
@click="initializeStallCreation"
class="w-full h-full p-6 flex flex-col items-center justify-center min-h-[200px] hover:bg-muted/30 transition-colors"
>
<div class="w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center mb-3">
<Plus class="w-6 h-6 text-primary" />
</div>
<h3 class="text-lg font-semibold text-foreground mb-1">Create New Store</h3>
<p class="text-sm text-muted-foreground text-center">
Add another store to expand your marketplace presence
</p>
</button>
</div>
</div>
<!-- Active Store Dashboard (shown when a store is selected) -->
<div v-if="activeStall">
<!-- Header -->
<div class="flex items-center justify-between">
<div class="flex items-center justify-between mb-6">
<div>
<h2 class="text-2xl font-bold text-foreground">My Store</h2>
<p class="text-muted-foreground mt-1">Manage incoming orders and your products</p>
<div class="flex items-center gap-3 mb-2">
<Button @click="activeStallId = null" variant="ghost" size="sm">
Back to Stores
</Button>
<div class="h-4 w-px bg-border"></div>
<Badge variant="secondary">{{ activeStall.currency }}</Badge>
</div>
<h2 class="text-2xl font-bold text-foreground">{{ activeStall.name }}</h2>
<p class="text-muted-foreground mt-1">{{ activeStall.config?.description || 'Manage incoming orders and your products' }}</p>
</div>
<div class="flex items-center gap-3">
<Button @click="navigateToMarket" variant="outline">
@ -359,8 +443,10 @@
</div>
</div>
</div>
</div> <!-- End of Store Content wrapper -->
</div>
</div> <!-- End of Active Store Dashboard -->
</div> <!-- End of Stores Grid section -->
</div> <!-- End of main container -->
<!-- Create Stall Dialog -->
<Dialog v-model:open="showStallDialog">
@ -608,7 +694,7 @@ import {
} from 'lucide-vue-next'
import type { OrderStatus } from '@/modules/market/stores/market'
import type { NostrmarketService } from '../services/nostrmarketService'
import type { NostrmarketAPI, Merchant, Zone, CreateStallRequest } from '../services/nostrmarketAPI'
import type { NostrmarketAPI, Merchant, Zone, Stall, CreateStallRequest } from '../services/nostrmarketAPI'
import { auth } from '@/composables/useAuthService'
import { useToast } from '@/core/composables/useToast'
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
@ -627,6 +713,14 @@ const merchantCheckError = ref<string | null>(null)
const isCreatingMerchant = ref(false)
const merchantCreateError = ref<string | null>(null)
// Multiple stalls state management
const userStalls = ref<Stall[]>([])
const activeStallId = ref<string | null>(null)
const isLoadingStalls = ref(false)
const activeStall = computed(() =>
userStalls.value.find(stall => stall.id === activeStallId.value)
)
// Stall creation state
const isCreatingStall = ref(false)
const stallCreateError = ref<string | null>(null)
@ -698,16 +792,7 @@ const userHasMerchantProfile = computed(() => {
})
const userHasStalls = computed(() => {
// Check if user has any stalls in the market store
const currentUserPubkey = auth.currentUser?.value?.pubkey
if (!currentUserPubkey) return false
// Check if any stalls belong to the current user
const userStalls = marketStore.stalls.filter(stall =>
stall.pubkey === currentUserPubkey
)
return userStalls.length > 0
return userStalls.value.length > 0
})
@ -1027,14 +1112,33 @@ const loadAvailableZones = async () => {
const loadStallsList = async () => {
const currentUser = auth.currentUser?.value
if (!currentUser?.wallets?.length) return
console.log('Loading stalls list - currentUser:', !!currentUser, 'wallets:', currentUser?.wallets?.length)
if (!currentUser?.wallets?.length) {
console.log('No user or wallets available, skipping stalls loading')
return
}
isLoadingStalls.value = true
try {
console.log('Calling getStalls with inkey:', currentUser.wallets[0].inkey.substring(0, 8) + '...')
const stalls = await nostrmarketAPI.getStalls(currentUser.wallets[0].inkey)
// Update the merchant stalls list - this could be connected to a store if needed
console.log('Updated stalls list:', stalls.length)
userStalls.value = stalls || []
console.log('Loaded user stalls:', {
stallsCount: stalls?.length || 0,
stalls: stalls?.map(s => ({ id: s.id, name: s.name }))
})
// If there are stalls but no active one selected, select the first
if (stalls?.length > 0 && !activeStallId.value) {
activeStallId.value = stalls[0].id
console.log('Set active stall ID to:', stalls[0].id)
}
} catch (error) {
console.error('Failed to load stalls:', error)
userStalls.value = []
} finally {
isLoadingStalls.value = false
}
}
@ -1159,6 +1263,20 @@ const createStall = async (formData: any) => {
}
}
// Missing functions for stall management
const manageStall = (stallId: string) => {
console.log('Managing stall:', stallId)
// Set as active stall for the dashboard view
activeStallId.value = stallId
}
const viewStallProducts = (stallId: string) => {
console.log('Viewing products for stall:', stallId)
// TODO: Navigate to products view for specific stall
// For now, set as active stall and scroll to products section
activeStallId.value = stallId
}
const navigateToMarket = () => router.push('/market')
const viewAllOrders = () => router.push('/market-dashboard?tab=orders')
const generateBulkInvoices = () => console.log('Generate bulk invoices')
@ -1219,6 +1337,14 @@ const checkMerchantProfile = async () => {
onMounted(async () => {
console.log('Merchant Store component loaded')
await checkMerchantProfile()
// Load stalls if merchant profile exists
if (merchantProfile.value) {
console.log('Merchant profile exists, loading stalls...')
await loadStallsList()
} else {
console.log('No merchant profile found, skipping stalls loading')
}
})
// Watch for auth changes and re-check merchant profile

View file

@ -212,7 +212,7 @@ export class NostrmarketAPI extends BaseService {
async getStalls(walletInkey: string): Promise<Stall[]> {
try {
const stalls = await this.request<Stall[]>(
'/api/v1/stalls',
'/api/v1/stall',
walletInkey,
{ method: 'GET' }
)