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:
parent
e6107839a0
commit
6f68c2320e
2 changed files with 162 additions and 36 deletions
|
|
@ -47,28 +47,112 @@
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- No Stalls Empty State (has merchant profile but no stalls) -->
|
<!-- Stores Grid (shown when merchant profile exists) -->
|
||||||
<div v-else-if="!userHasStalls" class="flex flex-col items-center justify-center py-12">
|
<div v-else>
|
||||||
<div class="w-24 h-24 bg-muted rounded-full flex items-center justify-center mb-6">
|
<!-- Header Section -->
|
||||||
<Store class="w-12 h-12 text-muted-foreground" />
|
<div class="mb-8">
|
||||||
</div>
|
<div class="flex items-center justify-between mb-2">
|
||||||
<h2 class="text-2xl font-bold text-foreground mb-2">Create Your First Store</h2>
|
<div>
|
||||||
<p class="text-muted-foreground text-center mb-6 max-w-md">
|
<h2 class="text-2xl font-bold text-foreground">My Stores</h2>
|
||||||
Great! You have a merchant profile. Now create your first store (stall) to start listing products and receiving orders.
|
<p class="text-muted-foreground mt-1">
|
||||||
|
Manage your stores and products
|
||||||
</p>
|
</p>
|
||||||
<Button @click="initializeStallCreation" variant="default" size="lg">
|
</div>
|
||||||
<Plus class="w-5 h-5 mr-2" />
|
<Button @click="navigateToMarket" variant="outline">
|
||||||
Create Store
|
<Store class="w-4 h-4 mr-2" />
|
||||||
|
Browse Market
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Store Content (shown when user has a store) -->
|
<!-- Loading State for Stalls -->
|
||||||
<div v-else>
|
<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 -->
|
<!-- Header -->
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between mb-6">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-2xl font-bold text-foreground">My Store</h2>
|
<div class="flex items-center gap-3 mb-2">
|
||||||
<p class="text-muted-foreground mt-1">Manage incoming orders and your products</p>
|
<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>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<Button @click="navigateToMarket" variant="outline">
|
<Button @click="navigateToMarket" variant="outline">
|
||||||
|
|
@ -359,8 +443,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div> <!-- End of Store Content wrapper -->
|
</div> <!-- End of Active Store Dashboard -->
|
||||||
</div>
|
</div> <!-- End of Stores Grid section -->
|
||||||
|
|
||||||
|
</div> <!-- End of main container -->
|
||||||
|
|
||||||
<!-- Create Stall Dialog -->
|
<!-- Create Stall Dialog -->
|
||||||
<Dialog v-model:open="showStallDialog">
|
<Dialog v-model:open="showStallDialog">
|
||||||
|
|
@ -608,7 +694,7 @@ import {
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import type { OrderStatus } from '@/modules/market/stores/market'
|
import type { OrderStatus } from '@/modules/market/stores/market'
|
||||||
import type { NostrmarketService } from '../services/nostrmarketService'
|
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 { auth } from '@/composables/useAuthService'
|
||||||
import { useToast } from '@/core/composables/useToast'
|
import { useToast } from '@/core/composables/useToast'
|
||||||
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
||||||
|
|
@ -627,6 +713,14 @@ const merchantCheckError = ref<string | null>(null)
|
||||||
const isCreatingMerchant = ref(false)
|
const isCreatingMerchant = ref(false)
|
||||||
const merchantCreateError = ref<string | null>(null)
|
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
|
// Stall creation state
|
||||||
const isCreatingStall = ref(false)
|
const isCreatingStall = ref(false)
|
||||||
const stallCreateError = ref<string | null>(null)
|
const stallCreateError = ref<string | null>(null)
|
||||||
|
|
@ -698,16 +792,7 @@ const userHasMerchantProfile = computed(() => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const userHasStalls = computed(() => {
|
const userHasStalls = computed(() => {
|
||||||
// Check if user has any stalls in the market store
|
return userStalls.value.length > 0
|
||||||
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
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1027,14 +1112,33 @@ const loadAvailableZones = async () => {
|
||||||
|
|
||||||
const loadStallsList = async () => {
|
const loadStallsList = async () => {
|
||||||
const currentUser = auth.currentUser?.value
|
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 {
|
try {
|
||||||
|
console.log('Calling getStalls with inkey:', currentUser.wallets[0].inkey.substring(0, 8) + '...')
|
||||||
const stalls = await nostrmarketAPI.getStalls(currentUser.wallets[0].inkey)
|
const stalls = await nostrmarketAPI.getStalls(currentUser.wallets[0].inkey)
|
||||||
// Update the merchant stalls list - this could be connected to a store if needed
|
userStalls.value = stalls || []
|
||||||
console.log('Updated stalls list:', stalls.length)
|
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) {
|
} catch (error) {
|
||||||
console.error('Failed to load stalls:', 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 navigateToMarket = () => router.push('/market')
|
||||||
const viewAllOrders = () => router.push('/market-dashboard?tab=orders')
|
const viewAllOrders = () => router.push('/market-dashboard?tab=orders')
|
||||||
const generateBulkInvoices = () => console.log('Generate bulk invoices')
|
const generateBulkInvoices = () => console.log('Generate bulk invoices')
|
||||||
|
|
@ -1219,6 +1337,14 @@ const checkMerchantProfile = async () => {
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
console.log('Merchant Store component loaded')
|
console.log('Merchant Store component loaded')
|
||||||
await checkMerchantProfile()
|
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
|
// Watch for auth changes and re-check merchant profile
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ export class NostrmarketAPI extends BaseService {
|
||||||
async getStalls(walletInkey: string): Promise<Stall[]> {
|
async getStalls(walletInkey: string): Promise<Stall[]> {
|
||||||
try {
|
try {
|
||||||
const stalls = await this.request<Stall[]>(
|
const stalls = await this.request<Stall[]>(
|
||||||
'/api/v1/stalls',
|
'/api/v1/stall',
|
||||||
walletInkey,
|
walletInkey,
|
||||||
{ method: 'GET' }
|
{ method: 'GET' }
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue