- Removed unnecessary imports and streamlined the LoadingErrorState component by eliminating redundant props. - Improved keyboard handling in MarketSearchBar to support basic Escape key functionality and enhanced keyboard shortcuts. - Updated MerchantStore and MarketPage components to utilize the revised LoadingErrorState and MarketSearchBar, ensuring consistent loading/error handling and search capabilities. - Enhanced StallView to provide better category filtering and product search experience. These changes improve code clarity and maintainability while enhancing user interaction across the market module.
643 lines
23 KiB
Vue
643 lines
23 KiB
Vue
<template>
|
|
<div class="space-y-6">
|
|
<!-- Loading State -->
|
|
<div v-if="isLoadingMerchant" class="flex justify-center items-center py-12">
|
|
<div class="flex flex-col items-center space-y-4">
|
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
|
<p class="text-gray-600">Loading your merchant profile...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Error State -->
|
|
<div v-else-if="merchantCheckError" class="flex justify-center items-center py-12">
|
|
<div class="text-center">
|
|
<h2 class="text-2xl font-bold text-red-600 mb-4">Error Loading Merchant Status</h2>
|
|
<p class="text-gray-600 mb-4">{{ merchantCheckError }}</p>
|
|
<Button @click="checkMerchantProfile" variant="outline">
|
|
Try Again
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Content -->
|
|
<div v-else>
|
|
<!-- No Merchant Profile Empty State -->
|
|
<div v-if="!userHasMerchantProfile" 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">
|
|
<User class="w-12 h-12 text-muted-foreground" />
|
|
</div>
|
|
<h2 class="text-2xl font-bold text-foreground mb-2">Create Your Merchant Profile</h2>
|
|
<p class="text-muted-foreground text-center mb-6 max-w-md">
|
|
Before you can create a store, you need to set up your merchant profile. This will create your merchant identity on the Nostr marketplace.
|
|
</p>
|
|
<Button
|
|
@click="createMerchantProfile"
|
|
variant="default"
|
|
size="lg"
|
|
:disabled="isCreatingMerchant"
|
|
>
|
|
<div v-if="isCreatingMerchant" class="flex items-center">
|
|
<div class="animate-spin rounded-full h-5 w-5 border-b-2 border-white mr-2"></div>
|
|
<span>Creating...</span>
|
|
</div>
|
|
<div v-else class="flex items-center">
|
|
<Plus class="w-5 h-5 mr-2" />
|
|
<span>Create Merchant Profile</span>
|
|
</div>
|
|
</Button>
|
|
</div>
|
|
|
|
<!-- 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 -->
|
|
<StoreCard
|
|
v-for="stall in userStalls"
|
|
:key="stall.id"
|
|
:stall="stall"
|
|
@manage="manageStall"
|
|
@view-products="viewStallProducts"
|
|
/>
|
|
|
|
<!-- 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="showCreateStoreDialog = true"
|
|
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="space-y-4 mb-6">
|
|
<!-- Top row with back button and currency -->
|
|
<div class="flex items-center gap-3">
|
|
<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>
|
|
|
|
<!-- Store info and actions -->
|
|
<div class="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
|
|
<div class="flex-1">
|
|
<h2 class="text-xl sm:text-2xl font-bold text-foreground">{{ activeStall.name }}</h2>
|
|
<p class="text-sm sm:text-base text-muted-foreground mt-1">{{ activeStall.config?.description || 'Manage incoming orders and your products' }}</p>
|
|
</div>
|
|
<div class="flex flex-col sm:flex-row gap-2 sm:gap-3">
|
|
<Button @click="navigateToMarket" variant="outline" class="w-full sm:w-auto">
|
|
<Store class="w-4 h-4 mr-2" />
|
|
<span class="sm:inline">Browse Market</span>
|
|
</Button>
|
|
<Button @click="showCreateProductDialog = true" variant="default" class="w-full sm:w-auto">
|
|
<Plus class="w-4 h-4 mr-2" />
|
|
<span>Add Product</span>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Store Stats -->
|
|
<div class="grid grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-6">
|
|
<!-- Incoming Orders -->
|
|
<div class="bg-card p-4 sm:p-6 rounded-lg border shadow-sm">
|
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
|
|
<div class="flex-1">
|
|
<p class="text-xs sm:text-sm font-medium text-muted-foreground">Incoming Orders</p>
|
|
<p class="text-xl sm:text-2xl font-bold text-foreground">{{ storeStats.incomingOrders }}</p>
|
|
</div>
|
|
<div class="w-10 h-10 sm:w-12 sm:h-12 bg-primary/10 rounded-lg flex items-center justify-center flex-shrink-0">
|
|
<Package class="w-5 h-5 sm:w-6 sm:h-6 text-primary" />
|
|
</div>
|
|
</div>
|
|
<div class="mt-3 sm:mt-4">
|
|
<div class="flex flex-wrap items-center text-xs sm:text-sm text-muted-foreground gap-1">
|
|
<span>{{ storeStats.pendingOrders }} pending</span>
|
|
<span class="hidden sm:inline mx-1">•</span>
|
|
<span>{{ storeStats.paidOrders }} paid</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Total Sales -->
|
|
<div class="bg-card p-4 sm:p-6 rounded-lg border shadow-sm">
|
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
|
|
<div class="flex-1">
|
|
<p class="text-xs sm:text-sm font-medium text-muted-foreground">Total Sales</p>
|
|
<p class="text-xl sm:text-2xl font-bold text-foreground">{{ formatPrice(storeStats.totalSales, 'sat') }}</p>
|
|
</div>
|
|
<div class="w-10 h-10 sm:w-12 sm:h-12 bg-green-500/10 rounded-lg flex items-center justify-center flex-shrink-0">
|
|
<DollarSign class="w-5 h-5 sm:w-6 sm:h-6 text-green-500" />
|
|
</div>
|
|
</div>
|
|
<div class="mt-3 sm:mt-4">
|
|
<div class="flex items-center text-xs sm:text-sm text-muted-foreground">
|
|
<span>Last 30 days</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Products -->
|
|
<div class="bg-card p-4 sm:p-6 rounded-lg border shadow-sm">
|
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
|
|
<div class="flex-1">
|
|
<p class="text-xs sm:text-sm font-medium text-muted-foreground">Products</p>
|
|
<p class="text-xl sm:text-2xl font-bold text-foreground">{{ stallProducts.length }}</p>
|
|
</div>
|
|
<div class="w-10 h-10 sm:w-12 sm:h-12 bg-purple-500/10 rounded-lg flex items-center justify-center flex-shrink-0">
|
|
<Store class="w-5 h-5 sm:w-6 sm:h-6 text-purple-500" />
|
|
</div>
|
|
</div>
|
|
<div class="mt-3 sm:mt-4">
|
|
<div class="flex items-center text-xs sm:text-sm text-muted-foreground">
|
|
<span>{{ stallProducts.filter(p => p.active).length }} active</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Customer Satisfaction -->
|
|
<div class="bg-card p-4 sm:p-6 rounded-lg border shadow-sm">
|
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
|
|
<div class="flex-1">
|
|
<p class="text-xs sm:text-sm font-medium text-muted-foreground">Satisfaction</p>
|
|
<p class="text-xl sm:text-2xl font-bold text-foreground">{{ storeStats.satisfaction }}%</p>
|
|
</div>
|
|
<div class="w-10 h-10 sm:w-12 sm:h-12 bg-yellow-500/10 rounded-lg flex items-center justify-center flex-shrink-0">
|
|
<Star class="w-5 h-5 sm:w-6 sm:h-6 text-yellow-500" />
|
|
</div>
|
|
</div>
|
|
<div class="mt-3 sm:mt-4">
|
|
<div class="flex items-center text-xs sm:text-sm text-muted-foreground">
|
|
<span>{{ storeStats.totalReviews }} reviews</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Products Section -->
|
|
<div class="mt-8">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h3 class="text-xl font-semibold text-foreground">Products</h3>
|
|
<Button @click="showCreateProductDialog = true" variant="default" size="sm">
|
|
<Plus class="w-4 h-4 mr-2" />
|
|
Add Product
|
|
</Button>
|
|
</div>
|
|
|
|
<!-- Loading Products -->
|
|
<div v-if="isLoadingProducts" class="flex items-center justify-center py-12">
|
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
|
<span class="ml-2 text-muted-foreground">Loading products...</span>
|
|
</div>
|
|
|
|
<!-- No Products -->
|
|
<div v-else-if="stallProducts.length === 0" class="text-center py-12">
|
|
<div class="w-16 h-16 mx-auto mb-4 bg-muted/50 rounded-full flex items-center justify-center">
|
|
<Package class="w-8 h-8 text-muted-foreground" />
|
|
</div>
|
|
<h4 class="text-lg font-medium text-foreground mb-2">No Products Yet</h4>
|
|
<p class="text-muted-foreground mb-6">Start selling by adding your first product</p>
|
|
<Button @click="showCreateProductDialog = true" variant="default">
|
|
<Plus class="w-4 h-4 mr-2" />
|
|
Add Your First Product
|
|
</Button>
|
|
</div>
|
|
|
|
<!-- Products Grid -->
|
|
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<div
|
|
v-for="product in stallProducts"
|
|
:key="product.id"
|
|
class="bg-card p-6 rounded-lg border shadow-sm hover:shadow-md transition-shadow"
|
|
>
|
|
<!-- Product Image -->
|
|
<div class="aspect-square mb-4 bg-muted rounded-lg flex items-center justify-center">
|
|
<img
|
|
v-if="product.images?.[0]"
|
|
:src="product.images[0]"
|
|
:alt="product.name"
|
|
class="w-full h-full object-cover rounded-lg"
|
|
/>
|
|
<Package v-else class="w-12 h-12 text-muted-foreground" />
|
|
</div>
|
|
|
|
<!-- Product Info -->
|
|
<div class="space-y-3">
|
|
<div>
|
|
<h4 class="font-semibold text-foreground">{{ product.name }}</h4>
|
|
<p v-if="product.config?.description" class="text-sm text-muted-foreground mt-1">
|
|
{{ product.config.description }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<span class="text-lg font-bold text-foreground">
|
|
{{ product.price }} {{ product.config?.currency || activeStall?.currency || 'sat' }}
|
|
</span>
|
|
<div class="text-sm text-muted-foreground">
|
|
Qty: {{ product.quantity }}
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<Badge :variant="product.active ? 'default' : 'secondary'">
|
|
{{ product.active ? 'Active' : 'Inactive' }}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Product Categories -->
|
|
<div v-if="product.categories?.length" class="flex flex-wrap gap-1">
|
|
<Badge
|
|
v-for="category in product.categories"
|
|
:key="category"
|
|
variant="outline"
|
|
class="text-xs"
|
|
>
|
|
{{ category }}
|
|
</Badge>
|
|
</div>
|
|
|
|
<!-- Product Actions -->
|
|
<div class="flex justify-end pt-2 border-t">
|
|
<Button
|
|
@click="editProduct(product)"
|
|
variant="ghost"
|
|
size="sm"
|
|
>
|
|
Edit
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create Store Dialog -->
|
|
<CreateStoreDialog
|
|
:is-open="showCreateStoreDialog"
|
|
@close="showCreateStoreDialog = false"
|
|
@created="onStoreCreated"
|
|
/>
|
|
|
|
<!-- Create Product Dialog -->
|
|
<CreateProductDialog
|
|
:is-open="showCreateProductDialog"
|
|
:stall="activeStall"
|
|
:product="editingProduct"
|
|
@close="closeProductDialog"
|
|
@created="onProductCreated"
|
|
@updated="onProductUpdated"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onMounted, watch } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { useMarketStore } from '@/modules/market/stores/market'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import {
|
|
Package,
|
|
Store,
|
|
DollarSign,
|
|
Star,
|
|
Plus,
|
|
User
|
|
} from 'lucide-vue-next'
|
|
import type { NostrmarketAPI, Merchant, Stall } from '../services/nostrmarketAPI'
|
|
import type { Product } from '../types/market'
|
|
import { mapApiResponseToProduct } from '../types/market'
|
|
import { auth } from '@/composables/useAuthService'
|
|
import { useToast } from '@/core/composables/useToast'
|
|
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
|
import CreateStoreDialog from './CreateStoreDialog.vue'
|
|
import CreateProductDialog from './CreateProductDialog.vue'
|
|
import StoreCard from './StoreCard.vue'
|
|
|
|
const router = useRouter()
|
|
const marketStore = useMarketStore()
|
|
const nostrmarketAPI = injectService(SERVICE_TOKENS.NOSTRMARKET_API) as NostrmarketAPI
|
|
const paymentService = injectService(SERVICE_TOKENS.PAYMENT_SERVICE) as any
|
|
const toast = useToast()
|
|
|
|
// Local state
|
|
const merchantProfile = ref<Merchant | null>(null)
|
|
const isLoadingMerchant = ref(false)
|
|
const merchantCheckError = ref<string | null>(null)
|
|
const isCreatingMerchant = ref(false)
|
|
|
|
// 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)
|
|
)
|
|
|
|
// Product management state
|
|
const stallProducts = ref<Product[]>([])
|
|
const isLoadingProducts = ref(false)
|
|
|
|
// Dialog state
|
|
const showCreateStoreDialog = ref(false)
|
|
const showCreateProductDialog = ref(false)
|
|
const editingProduct = ref<Product | null>(null)
|
|
|
|
// Computed properties
|
|
const userHasMerchantProfile = computed(() => {
|
|
return merchantProfile.value !== null
|
|
})
|
|
|
|
const userHasStalls = computed(() => {
|
|
return userStalls.value.length > 0
|
|
})
|
|
|
|
const storeStats = computed(() => {
|
|
const currentUserPubkey = auth.currentUser?.value?.pubkey
|
|
if (!currentUserPubkey) {
|
|
return {
|
|
incomingOrders: 0,
|
|
pendingOrders: 0,
|
|
paidOrders: 0,
|
|
totalSales: 0,
|
|
satisfaction: 0,
|
|
totalReviews: 0
|
|
}
|
|
}
|
|
|
|
// Filter orders to only count those where current user is the seller
|
|
const myOrders = Object.values(marketStore.orders).filter(o => o.sellerPubkey === currentUserPubkey)
|
|
const now = Date.now() / 1000
|
|
const thirtyDaysAgo = now - (30 * 24 * 60 * 60)
|
|
|
|
return {
|
|
incomingOrders: myOrders.filter(o => o.status === 'pending').length,
|
|
pendingOrders: myOrders.filter(o => o.status === 'pending').length,
|
|
paidOrders: myOrders.filter(o => o.status === 'paid').length,
|
|
totalSales: myOrders
|
|
.filter(o => o.status === 'paid' && o.createdAt > thirtyDaysAgo)
|
|
.reduce((sum, o) => sum + o.total, 0),
|
|
satisfaction: userHasStalls.value ? 95 : 0,
|
|
totalReviews: 0
|
|
}
|
|
})
|
|
|
|
// Methods
|
|
const formatPrice = (price: number, currency: string) => {
|
|
return marketStore.formatPrice(price, currency)
|
|
}
|
|
|
|
const createMerchantProfile = async () => {
|
|
const currentUser = auth.currentUser?.value
|
|
if (!currentUser) {
|
|
console.error('No authenticated user for merchant creation')
|
|
return
|
|
}
|
|
|
|
const userWallets = currentUser.wallets || []
|
|
if (userWallets.length === 0) {
|
|
console.error('No wallets available for merchant creation')
|
|
toast.error('No wallet available. Please ensure you have at least one wallet configured.')
|
|
return
|
|
}
|
|
|
|
const wallet = userWallets[0]
|
|
if (!wallet.adminkey) {
|
|
console.error('Wallet missing admin key for merchant creation')
|
|
toast.error('Wallet missing admin key. Admin key is required to create merchant profiles.')
|
|
return
|
|
}
|
|
|
|
isCreatingMerchant.value = true
|
|
|
|
try {
|
|
const merchantData = {
|
|
config: {}
|
|
}
|
|
|
|
const newMerchant = await nostrmarketAPI.createMerchant(wallet.adminkey, merchantData)
|
|
|
|
merchantProfile.value = newMerchant
|
|
|
|
toast.success('Merchant profile created successfully! You can now create your first store.')
|
|
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : 'Failed to create merchant profile'
|
|
console.error('Error creating merchant profile:', error)
|
|
toast.error(`Failed to create merchant profile: ${errorMessage}`)
|
|
} finally {
|
|
isCreatingMerchant.value = false
|
|
}
|
|
}
|
|
|
|
const loadStallsList = async () => {
|
|
const currentUser = auth.currentUser?.value
|
|
|
|
if (!currentUser?.wallets?.length) {
|
|
return
|
|
}
|
|
|
|
isLoadingStalls.value = true
|
|
|
|
const inkey = paymentService.getPreferredWalletInvoiceKey()
|
|
if (!inkey) {
|
|
console.error('No wallet invoice key available')
|
|
return
|
|
}
|
|
|
|
try {
|
|
const stalls = await nostrmarketAPI.getStalls(inkey)
|
|
userStalls.value = stalls || []
|
|
|
|
// If there are stalls but no active one selected, select the first
|
|
if (stalls?.length > 0 && !activeStallId.value) {
|
|
activeStallId.value = stalls[0].id!
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load stalls:', error)
|
|
userStalls.value = []
|
|
} finally {
|
|
isLoadingStalls.value = false
|
|
}
|
|
}
|
|
|
|
const loadStallProducts = async () => {
|
|
if (!activeStall.value) return
|
|
|
|
const currentUser = auth.currentUser?.value
|
|
if (!currentUser?.wallets?.length) return
|
|
|
|
isLoadingProducts.value = true
|
|
|
|
const inkey = paymentService.getPreferredWalletInvoiceKey()
|
|
if (!inkey) {
|
|
console.error('No wallet invoice key available')
|
|
return
|
|
}
|
|
|
|
try {
|
|
const products = await nostrmarketAPI.getProducts(
|
|
inkey,
|
|
activeStall.value.id!
|
|
)
|
|
// Convert API responses to domain models using clean mapping function
|
|
const enrichedProducts = (products || []).map(product =>
|
|
mapApiResponseToProduct(
|
|
product,
|
|
activeStall.value?.name || 'Unknown Stall',
|
|
activeStall.value?.currency || 'sats'
|
|
)
|
|
)
|
|
stallProducts.value = enrichedProducts
|
|
|
|
// 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 = []
|
|
} finally {
|
|
isLoadingProducts.value = false
|
|
}
|
|
}
|
|
|
|
const manageStall = (stallId: string) => {
|
|
activeStallId.value = stallId
|
|
}
|
|
|
|
const viewStallProducts = (stallId: string) => {
|
|
activeStallId.value = stallId
|
|
}
|
|
|
|
const navigateToMarket = () => router.push('/market')
|
|
|
|
const checkMerchantProfile = async () => {
|
|
const currentUser = auth.currentUser?.value
|
|
if (!currentUser) return
|
|
|
|
const userWallets = currentUser.wallets || []
|
|
if (userWallets.length === 0) {
|
|
console.warn('No wallets available for merchant check')
|
|
return
|
|
}
|
|
|
|
const wallet = userWallets[0]
|
|
if (!wallet.inkey) {
|
|
console.warn('Wallet missing invoice key for merchant check')
|
|
return
|
|
}
|
|
|
|
isLoadingMerchant.value = true
|
|
merchantCheckError.value = null
|
|
|
|
try {
|
|
const merchant = await nostrmarketAPI.getMerchant(wallet.inkey)
|
|
merchantProfile.value = merchant
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : 'Failed to check merchant profile'
|
|
console.error('Error checking merchant profile:', error)
|
|
merchantCheckError.value = errorMessage
|
|
merchantProfile.value = null
|
|
} finally {
|
|
isLoadingMerchant.value = false
|
|
}
|
|
}
|
|
|
|
// Event handlers
|
|
const onStoreCreated = async (_stall: Stall) => {
|
|
await loadStallsList()
|
|
toast.success('Store created successfully!')
|
|
}
|
|
|
|
const onProductCreated = async (_product: Product) => {
|
|
await loadStallProducts()
|
|
toast.success('Product created successfully!')
|
|
}
|
|
|
|
const onProductUpdated = async (_product: Product) => {
|
|
await loadStallProducts()
|
|
toast.success('Product updated successfully!')
|
|
}
|
|
|
|
const editProduct = (product: Product) => {
|
|
editingProduct.value = product
|
|
showCreateProductDialog.value = true
|
|
}
|
|
|
|
const closeProductDialog = () => {
|
|
showCreateProductDialog.value = false
|
|
editingProduct.value = null
|
|
}
|
|
|
|
// Lifecycle
|
|
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
|
|
watch(() => auth.currentUser?.value?.pubkey, async (newPubkey, oldPubkey) => {
|
|
if (newPubkey !== oldPubkey) {
|
|
console.log('User changed, re-checking merchant profile')
|
|
await checkMerchantProfile()
|
|
}
|
|
})
|
|
|
|
// Watch for active stall changes and load products
|
|
watch(() => activeStallId.value, async (newStallId) => {
|
|
if (newStallId) {
|
|
await loadStallProducts()
|
|
} else {
|
|
stallProducts.value = []
|
|
}
|
|
})
|
|
</script>
|