- Introduce a dialog for creating new stores, allowing users to input store name, description, currency, and shipping zones. - Add functionality to manage shipping zones, including the ability to create new zones and select existing ones. - Enhance the stall creation process with error handling and loading states, providing better user feedback during store setup. - Update the NostrmarketAPI to support fetching available currencies and shipping zones, improving integration with the backend services. These changes streamline the store creation experience for merchants, ensuring a more intuitive and guided process. Refactor getCurrencies method in NostrmarketAPI to improve currency retrieval logic - Introduce base currencies and enhance the logic to combine them with API currencies, ensuring no duplicates. - Update debug logging to provide clearer information on currency retrieval outcomes. - Simplify fallback mechanism to use base currencies directly in case of API failures. These changes enhance the reliability and clarity of currency data handling in the NostrmarketAPI. Refactor MerchantStore component to use NATIVE checkbox selection and add debug information - Replace Checkbox component with native input checkboxes for zone selection, simplifying the binding with v-model. - Enhance the user interface by adding debug information displaying the store name, selected zones count, and creation status. - These changes improve the clarity of the zone selection process and provide useful debugging insights during store creation. Enhance zone selection functionality in MerchantStore component - Replace v-model with native checkbox handling for zone selection, improving clarity and user interaction. - Add debug information to display currently selected zones, aiding in user understanding of their selections. - Implement a new method to manage zone toggling, ensuring accurate updates to the selected zones array. These changes streamline the zone selection process and provide better feedback for users during stall creation. Improve zone selection handling and debugging in MerchantStore component - Update zone selection to use a custom Checkbox component, enhancing user interaction and clarity. - Add detailed debug information for selected zones, including raw array output and type, to aid in troubleshooting. - Refactor the zone toggle logic to handle various input types, ensuring accurate updates to the selected zones array. These changes enhance the user experience during stall creation by providing better feedback and more robust zone selection functionality. Refactor Checkbox handling in MerchantStore component for improved zone selection - Update zone selection to utilize the Shadcn/UI Checkbox component with v-model for better state management. - Remove manual zone toggle logic and debug information, streamlining the component's functionality. - Enhance user interaction by following recommended patterns for checkbox usage, ensuring reliable selections. These changes improve the clarity and reliability of zone selection during stall creation. Refactor MerchantStore component to utilize Shadcn Form components and improve form handling - Replace existing form elements with Shadcn Form components (FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage) for better structure and validation. - Integrate vee-validate and zod for type-safe form validation, enhancing user experience and error handling. - Update shipping zone selection to use the new form structure, improving clarity and accessibility. - Implement form submission logic with validation checks, ensuring required fields are filled before submission. These changes enhance the overall form handling and user interaction during the store creation process.
1232 lines
42 KiB
Vue
1232 lines
42 KiB
Vue
<template>
|
||
<div class="space-y-6">
|
||
<!-- Loading State -->
|
||
<div v-if="isLoadingMerchant" class="flex flex-col items-center justify-center py-12">
|
||
<div class="w-16 h-16 mx-auto mb-4 bg-muted/50 rounded-full flex items-center justify-center">
|
||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
||
</div>
|
||
<h3 class="text-lg font-medium text-foreground mb-2">Checking Merchant Status</h3>
|
||
<p class="text-muted-foreground">Loading your merchant profile...</p>
|
||
</div>
|
||
|
||
<!-- Error State -->
|
||
<div v-else-if="merchantCheckError" class="flex flex-col items-center justify-center py-12">
|
||
<div class="w-16 h-16 mx-auto mb-4 bg-red-500/10 rounded-full flex items-center justify-center">
|
||
<AlertCircle class="w-8 h-8 text-red-500" />
|
||
</div>
|
||
<h3 class="text-lg font-medium text-foreground mb-2">Error Loading Merchant Status</h3>
|
||
<p class="text-muted-foreground mb-4">{{ merchantCheckError }}</p>
|
||
<Button @click="checkMerchantProfile" variant="outline">
|
||
Try Again
|
||
</Button>
|
||
</div>
|
||
|
||
<!-- No Merchant Profile Empty State -->
|
||
<div v-else-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>
|
||
|
||
<!-- 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) -->
|
||
<div v-else>
|
||
<!-- Header -->
|
||
<div class="flex items-center justify-between">
|
||
<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>
|
||
<div class="flex items-center gap-3">
|
||
<Button @click="navigateToMarket" variant="outline">
|
||
<Store class="w-4 h-4 mr-2" />
|
||
Browse Market
|
||
</Button>
|
||
<Button @click="addProduct" variant="default">
|
||
<Plus class="w-4 h-4 mr-2" />
|
||
Add Product
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Store Stats -->
|
||
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||
<!-- Incoming Orders -->
|
||
<div class="bg-card p-6 rounded-lg border shadow-sm">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-sm font-medium text-muted-foreground">Incoming Orders</p>
|
||
<p class="text-2xl font-bold text-foreground">{{ storeStats.incomingOrders }}</p>
|
||
</div>
|
||
<div class="w-12 h-12 bg-primary/10 rounded-lg flex items-center justify-center">
|
||
<Package class="w-6 h-6 text-primary" />
|
||
</div>
|
||
</div>
|
||
<div class="mt-4">
|
||
<div class="flex items-center text-sm text-muted-foreground">
|
||
<span>{{ storeStats.pendingOrders }} pending</span>
|
||
<span class="mx-2">•</span>
|
||
<span>{{ storeStats.paidOrders }} paid</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Total Sales -->
|
||
<div class="bg-card p-6 rounded-lg border shadow-sm">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-sm font-medium text-muted-foreground">Total Sales</p>
|
||
<p class="text-2xl font-bold text-foreground">{{ formatPrice(storeStats.totalSales, 'sat') }}</p>
|
||
</div>
|
||
<div class="w-12 h-12 bg-green-500/10 rounded-lg flex items-center justify-center">
|
||
<DollarSign class="w-6 h-6 text-green-500" />
|
||
</div>
|
||
</div>
|
||
<div class="mt-4">
|
||
<div class="flex items-center text-sm text-muted-foreground">
|
||
<span>Last 30 days</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Products -->
|
||
<div class="bg-card p-6 rounded-lg border shadow-sm">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-sm font-medium text-muted-foreground">Products</p>
|
||
<p class="text-2xl font-bold text-foreground">{{ storeStats.totalProducts }}</p>
|
||
</div>
|
||
<div class="w-12 h-12 bg-purple-500/10 rounded-lg flex items-center justify-center">
|
||
<Store class="w-6 h-6 text-purple-500" />
|
||
</div>
|
||
</div>
|
||
<div class="mt-4">
|
||
<div class="flex items-center text-sm text-muted-foreground">
|
||
<span>{{ storeStats.activeProducts }} active</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Customer Satisfaction -->
|
||
<div class="bg-card p-6 rounded-lg border shadow-sm">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-sm font-medium text-muted-foreground">Satisfaction</p>
|
||
<p class="text-2xl font-bold text-foreground">{{ storeStats.satisfaction }}%</p>
|
||
</div>
|
||
<div class="w-12 h-12 bg-yellow-500/10 rounded-lg flex items-center justify-center">
|
||
<Star class="w-6 h-6 text-yellow-500" />
|
||
</div>
|
||
</div>
|
||
<div class="mt-4">
|
||
<div class="flex items-center text-sm text-muted-foreground">
|
||
<span>{{ storeStats.totalReviews }} reviews</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Incoming Orders Section -->
|
||
<div class="bg-card rounded-lg border shadow-sm">
|
||
<div class="p-6 border-b border-border">
|
||
<h3 class="text-lg font-semibold text-foreground">Incoming Orders</h3>
|
||
<p class="text-sm text-muted-foreground mt-1">Orders waiting for your attention</p>
|
||
</div>
|
||
|
||
<div v-if="incomingOrders.length > 0" class="divide-y divide-border">
|
||
<div
|
||
v-for="order in incomingOrders"
|
||
:key="order.id"
|
||
class="p-6 hover:bg-muted/50 transition-colors"
|
||
>
|
||
<!-- Order Header -->
|
||
<div class="flex items-center justify-between mb-4">
|
||
<div class="flex items-center gap-3">
|
||
<div class="w-10 h-10 bg-primary/10 rounded-full flex items-center justify-center">
|
||
<Package class="w-5 h-5 text-primary" />
|
||
</div>
|
||
<div>
|
||
<h4 class="font-semibold text-foreground">Order #{{ order.id.slice(-8) }}</h4>
|
||
<p class="text-sm text-muted-foreground">
|
||
{{ formatDate(order.createdAt) }} • {{ formatPrice(order.total, order.currency) }}
|
||
</p>
|
||
<div class="flex items-center gap-2 mt-1">
|
||
<Badge :variant="getStatusVariant(order.status)">
|
||
{{ formatStatus(order.status) }}
|
||
</Badge>
|
||
<Badge v-if="order.paymentStatus === 'pending'" variant="secondary">
|
||
Payment Pending
|
||
</Badge>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex items-center gap-2">
|
||
<!-- Wallet Indicator -->
|
||
<div v-if="order.status === 'pending' && !order.lightningInvoice" class="text-xs text-muted-foreground mr-2">
|
||
<span>Wallet: {{ getFirstWalletName() }}</span>
|
||
</div>
|
||
|
||
<Button
|
||
v-if="order.status === 'pending' && !order.lightningInvoice"
|
||
@click="generateInvoice(order.id)"
|
||
:disabled="isGeneratingInvoice === order.id"
|
||
size="sm"
|
||
variant="default"
|
||
>
|
||
<div v-if="isGeneratingInvoice === order.id" class="flex items-center space-x-2">
|
||
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
|
||
<span>Generating...</span>
|
||
</div>
|
||
<div v-else class="flex items-center space-x-2">
|
||
<Zap class="w-4 h-4" />
|
||
<span>Generate Invoice</span>
|
||
</div>
|
||
</Button>
|
||
<Button
|
||
v-if="order.lightningInvoice"
|
||
@click="viewOrderDetails(order.id)"
|
||
size="sm"
|
||
variant="outline"
|
||
>
|
||
<Eye class="w-4 h-4 mr-2" />
|
||
View Details
|
||
</Button>
|
||
<Button
|
||
@click="processOrder(order.id)"
|
||
size="sm"
|
||
variant="outline"
|
||
>
|
||
<Check class="w-4 h-4 mr-2" />
|
||
Process
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Order Items -->
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||
<div>
|
||
<h5 class="font-medium text-foreground mb-2">Items</h5>
|
||
<div class="space-y-1">
|
||
<div
|
||
v-for="item in order.items"
|
||
:key="item.productId"
|
||
class="text-sm text-muted-foreground"
|
||
>
|
||
{{ item.productName }} × {{ item.quantity }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h5 class="font-medium text-foreground mb-2">Customer Info</h5>
|
||
<div class="space-y-1 text-sm text-muted-foreground">
|
||
<p v-if="order.contactInfo.email">
|
||
<span class="font-medium">Email:</span> {{ order.contactInfo.email }}
|
||
</p>
|
||
<p v-if="order.contactInfo.message">
|
||
<span class="font-medium">Message:</span> {{ order.contactInfo.message }}
|
||
</p>
|
||
<p v-if="order.contactInfo.address">
|
||
<span class="font-medium">Address:</span> {{ order.contactInfo.address }}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Payment Status -->
|
||
<div v-if="order.lightningInvoice" class="p-4 bg-green-500/10 border border-green-200 rounded-lg">
|
||
<div class="flex items-center justify-between">
|
||
<div class="flex items-center gap-2">
|
||
<CheckCircle class="w-5 h-5 text-green-600" />
|
||
<span class="text-sm font-medium text-green-900">Lightning Invoice Generated</span>
|
||
</div>
|
||
<div class="text-sm text-green-700">
|
||
Amount: {{ formatPrice(order.total, order.currency) }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-else class="p-4 bg-yellow-500/10 border border-yellow-200 rounded-lg">
|
||
<div class="flex items-center gap-2">
|
||
<AlertCircle class="w-5 h-5 text-yellow-600" />
|
||
<span class="text-sm font-medium text-yellow-900">Invoice Required</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-else class="p-6 text-center text-muted-foreground">
|
||
<Package class="w-12 h-12 mx-auto mb-3 text-muted-foreground/50" />
|
||
<p>No incoming orders</p>
|
||
<p class="text-sm">Orders from customers will appear here</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Quick Actions -->
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<!-- Order Management -->
|
||
<div class="bg-card p-6 rounded-lg border shadow-sm">
|
||
<h3 class="text-lg font-semibold text-foreground mb-4">Order Management</h3>
|
||
<div class="space-y-3">
|
||
<Button
|
||
@click="viewAllOrders"
|
||
variant="default"
|
||
class="w-full justify-start"
|
||
>
|
||
<Package class="w-4 h-4 mr-2" />
|
||
View All Orders
|
||
</Button>
|
||
<Button
|
||
@click="generateBulkInvoices"
|
||
variant="outline"
|
||
class="w-full justify-start"
|
||
>
|
||
<Zap class="w-4 h-4 mr-2" />
|
||
Generate Bulk Invoices
|
||
</Button>
|
||
<Button
|
||
@click="exportOrders"
|
||
variant="outline"
|
||
class="w-full justify-start"
|
||
>
|
||
<Download class="w-4 h-4 mr-2" />
|
||
Export Orders
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Store Management -->
|
||
<div class="bg-card p-6 rounded-lg border shadow-sm">
|
||
<h3 class="text-lg font-semibold text-foreground mb-4">Store Management</h3>
|
||
<div class="space-y-3">
|
||
<Button
|
||
@click="manageProducts"
|
||
variant="default"
|
||
class="w-full justify-start"
|
||
>
|
||
<Store class="w-4 h-4 mr-2" />
|
||
Manage Products
|
||
</Button>
|
||
<Button
|
||
@click="storeSettings"
|
||
variant="outline"
|
||
class="w-full justify-start"
|
||
>
|
||
<Settings class="w-4 h-4 mr-2" />
|
||
Store Settings
|
||
</Button>
|
||
<Button
|
||
@click="analytics"
|
||
variant="outline"
|
||
class="w-full justify-start"
|
||
>
|
||
<BarChart3 class="w-4 h-4 mr-2" />
|
||
View Analytics
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div> <!-- End of Store Content wrapper -->
|
||
</div>
|
||
|
||
<!-- Create Stall Dialog -->
|
||
<Dialog v-model:open="showStallDialog">
|
||
<DialogContent class="sm:max-w-2xl">
|
||
<DialogHeader>
|
||
<DialogTitle>Create New Store</DialogTitle>
|
||
</DialogHeader>
|
||
|
||
<form @submit="onSubmit" class="space-y-6 py-4">
|
||
<!-- Basic Store Info -->
|
||
<div class="space-y-4">
|
||
<FormField v-slot="{ componentField }" name="name">
|
||
<FormItem>
|
||
<FormLabel>Store Name *</FormLabel>
|
||
<FormControl>
|
||
<Input
|
||
placeholder="Enter your store name"
|
||
:disabled="isCreatingStall"
|
||
v-bind="componentField"
|
||
/>
|
||
</FormControl>
|
||
<FormDescription>
|
||
Choose a unique name for your store
|
||
</FormDescription>
|
||
<FormMessage />
|
||
</FormItem>
|
||
</FormField>
|
||
|
||
<FormField v-slot="{ componentField }" name="description">
|
||
<FormItem>
|
||
<FormLabel>Description</FormLabel>
|
||
<FormControl>
|
||
<Textarea
|
||
placeholder="Describe your store and products"
|
||
:disabled="isCreatingStall"
|
||
v-bind="componentField"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
</FormField>
|
||
|
||
<FormField v-slot="{ componentField }" name="currency">
|
||
<FormItem>
|
||
<FormLabel>Currency *</FormLabel>
|
||
<Select :disabled="isCreatingStall" v-bind="componentField">
|
||
<FormControl>
|
||
<SelectTrigger>
|
||
<SelectValue placeholder="Select currency" />
|
||
</SelectTrigger>
|
||
</FormControl>
|
||
<SelectContent>
|
||
<SelectItem v-for="currency in availableCurrencies" :key="currency" :value="currency">
|
||
{{ currency }}
|
||
</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
<FormMessage />
|
||
</FormItem>
|
||
</FormField>
|
||
</div>
|
||
|
||
<!-- Shipping Zones Section -->
|
||
<FormField name="selectedZones">
|
||
<FormItem>
|
||
<div class="mb-4">
|
||
<FormLabel class="text-base">Shipping Zones *</FormLabel>
|
||
<FormDescription>
|
||
Select existing zones or create new ones for your store
|
||
</FormDescription>
|
||
</div>
|
||
|
||
<!-- Existing Zones -->
|
||
<div v-if="availableZones.length > 0" class="space-y-3">
|
||
<FormLabel class="text-sm font-medium">Available Zones:</FormLabel>
|
||
<div class="space-y-2 max-h-32 overflow-y-auto">
|
||
<FormField
|
||
v-for="zone in availableZones"
|
||
:key="zone.id"
|
||
v-slot="{ value, handleChange }"
|
||
type="checkbox"
|
||
:value="zone.id"
|
||
:unchecked-value="false"
|
||
name="selectedZones"
|
||
>
|
||
<FormItem class="flex flex-row items-start space-x-3 space-y-0">
|
||
<FormControl>
|
||
<Checkbox
|
||
:model-value="value.includes(zone.id)"
|
||
@update:model-value="handleChange"
|
||
:disabled="isCreatingStall"
|
||
/>
|
||
</FormControl>
|
||
<FormLabel class="text-sm cursor-pointer font-normal">
|
||
{{ zone.name }} - {{ zone.cost }} {{ zone.currency }}
|
||
<span class="text-muted-foreground ml-1">({{ zone.countries.slice(0, 2).join(', ') }}{{ zone.countries.length > 2 ? '...' : '' }})</span>
|
||
</FormLabel>
|
||
</FormItem>
|
||
</FormField>
|
||
</div>
|
||
</div>
|
||
<FormMessage />
|
||
</FormItem>
|
||
</FormField>
|
||
|
||
<!-- Create New Zone -->
|
||
<div class="border-t pt-4">
|
||
<Label class="text-sm font-medium">Create New Zone:</Label>
|
||
<div class="space-y-3 mt-2">
|
||
<FormField v-slot="{ componentField }" name="newZone.name">
|
||
<FormItem>
|
||
<FormLabel>Zone Name</FormLabel>
|
||
<FormControl>
|
||
<Input
|
||
placeholder="e.g., Europe, Worldwide"
|
||
:disabled="isCreatingStall"
|
||
v-bind="componentField"
|
||
/>
|
||
</FormControl>
|
||
</FormItem>
|
||
</FormField>
|
||
|
||
<FormField v-slot="{ componentField }" name="newZone.cost">
|
||
<FormItem>
|
||
<FormLabel>Shipping Cost</FormLabel>
|
||
<FormControl>
|
||
<Input
|
||
type="number"
|
||
min="0"
|
||
:placeholder="`Cost in ${getFieldValue('currency') || 'sat'}`"
|
||
:disabled="isCreatingStall"
|
||
v-bind="componentField"
|
||
/>
|
||
</FormControl>
|
||
</FormItem>
|
||
</FormField>
|
||
|
||
<FormField v-slot="{ componentField }" name="newZone.selectedCountries">
|
||
<FormItem>
|
||
<FormLabel>Countries/Regions</FormLabel>
|
||
<FormControl>
|
||
<Select multiple :disabled="isCreatingStall" v-bind="componentField">
|
||
<SelectTrigger>
|
||
<SelectValue placeholder="Select countries/regions" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem v-for="country in countries" :key="country" :value="country">
|
||
{{ country }}
|
||
</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</FormControl>
|
||
<div v-if="getFieldValue('newZone')?.selectedCountries?.length > 0" class="mt-2">
|
||
<div class="flex flex-wrap gap-1">
|
||
<Badge
|
||
v-for="country in getFieldValue('newZone').selectedCountries"
|
||
:key="country"
|
||
variant="secondary"
|
||
class="text-xs"
|
||
>
|
||
{{ country }}
|
||
</Badge>
|
||
</div>
|
||
</div>
|
||
</FormItem>
|
||
</FormField>
|
||
|
||
<Button
|
||
@click="addNewZone"
|
||
type="button"
|
||
variant="outline"
|
||
size="sm"
|
||
:disabled="isCreatingStall || !getFieldValue('newZone')?.name || !getFieldValue('newZone')?.selectedCountries?.length"
|
||
>
|
||
<Plus class="w-4 h-4 mr-2" />
|
||
Add Zone
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Error Display -->
|
||
<div v-if="stallCreateError" class="text-sm text-destructive">
|
||
{{ stallCreateError }}
|
||
</div>
|
||
|
||
<div class="flex justify-end space-x-2 pt-4">
|
||
<Button
|
||
@click="showStallDialog = false"
|
||
variant="outline"
|
||
:disabled="isCreatingStall"
|
||
>
|
||
Cancel
|
||
</Button>
|
||
<Button
|
||
type="submit"
|
||
:disabled="isCreatingStall || !isFormValid"
|
||
>
|
||
<span v-if="isCreatingStall">Creating...</span>
|
||
<span v-else>Create Store</span>
|
||
</Button>
|
||
</div>
|
||
</form>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, onMounted, watch } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { useForm } from 'vee-validate'
|
||
import { toTypedSchema } from '@vee-validate/zod'
|
||
import * as z from 'zod'
|
||
import { useMarketStore } from '@/modules/market/stores/market'
|
||
import { Button } from '@/components/ui/button'
|
||
import { Badge } from '@/components/ui/badge'
|
||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||
import { Input } from '@/components/ui/input'
|
||
import { Label } from '@/components/ui/label'
|
||
import { Textarea } from '@/components/ui/textarea'
|
||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||
import { Checkbox } from '@/components/ui/checkbox'
|
||
import {
|
||
FormControl,
|
||
FormDescription,
|
||
FormField,
|
||
FormItem,
|
||
FormLabel,
|
||
FormMessage,
|
||
} from '@/components/ui/form'
|
||
import {
|
||
Package,
|
||
Store,
|
||
DollarSign,
|
||
Star,
|
||
Plus,
|
||
Zap,
|
||
Eye,
|
||
Check,
|
||
AlertCircle,
|
||
CheckCircle,
|
||
Download,
|
||
Settings,
|
||
BarChart3,
|
||
User
|
||
} 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 { auth } from '@/composables/useAuthService'
|
||
import { useToast } from '@/core/composables/useToast'
|
||
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
||
|
||
const router = useRouter()
|
||
const marketStore = useMarketStore()
|
||
const nostrmarketService = injectService(SERVICE_TOKENS.NOSTRMARKET_SERVICE) as NostrmarketService
|
||
const nostrmarketAPI = injectService(SERVICE_TOKENS.NOSTRMARKET_API) as NostrmarketAPI
|
||
const toast = useToast()
|
||
|
||
// Local state
|
||
const isGeneratingInvoice = ref<string | null>(null)
|
||
const merchantProfile = ref<Merchant | null>(null)
|
||
const isLoadingMerchant = ref(false)
|
||
const merchantCheckError = ref<string | null>(null)
|
||
const isCreatingMerchant = ref(false)
|
||
const merchantCreateError = ref<string | null>(null)
|
||
|
||
// Stall creation state
|
||
const isCreatingStall = ref(false)
|
||
const stallCreateError = ref<string | null>(null)
|
||
const showStallDialog = ref(false)
|
||
const availableCurrencies = ref<string[]>(['sat'])
|
||
const availableZones = ref<Zone[]>([])
|
||
const countries = ref([
|
||
'Free (digital)', 'Worldwide', 'Europe', 'Australia', 'Austria', 'Belgium', 'Brazil', 'Canada',
|
||
'Denmark', 'Finland', 'France', 'Germany', 'Greece', 'Hong Kong', 'Hungary',
|
||
'Ireland', 'Indonesia', 'Israel', 'Italy', 'Japan', 'Kazakhstan', 'Korea',
|
||
'Luxembourg', 'Malaysia', 'Mexico', 'Netherlands', 'New Zealand', 'Norway',
|
||
'Poland', 'Portugal', 'Romania', 'Russia', 'Saudi Arabia', 'Singapore',
|
||
'Spain', 'Sweden', 'Switzerland', 'Thailand', 'Turkey', 'Ukraine',
|
||
'United Kingdom', 'United States', 'Vietnam', 'China'
|
||
])
|
||
|
||
// Stall form schema
|
||
const stallFormSchema = toTypedSchema(z.object({
|
||
name: z.string().min(1, "Store name is required").max(100, "Store name must be less than 100 characters"),
|
||
description: z.string().max(500, "Description must be less than 500 characters").optional(),
|
||
currency: z.string().min(1, "Currency is required"),
|
||
wallet: z.string().optional(),
|
||
selectedZones: z.array(z.string()).min(1, "Select at least one shipping zone"),
|
||
newZone: z.object({
|
||
name: z.string().optional(),
|
||
cost: z.number().min(0, "Cost must be 0 or greater").optional(),
|
||
selectedCountries: z.array(z.string()).optional()
|
||
}).optional()
|
||
}))
|
||
|
||
// Form setup with vee-validate
|
||
const form = useForm({
|
||
validationSchema: stallFormSchema,
|
||
initialValues: {
|
||
name: '',
|
||
description: '',
|
||
currency: 'sat',
|
||
wallet: '',
|
||
selectedZones: [] as string[],
|
||
newZone: {
|
||
name: '',
|
||
cost: 0,
|
||
selectedCountries: [] as string[]
|
||
}
|
||
}
|
||
})
|
||
|
||
// Destructure form methods for easier access
|
||
const { setFieldValue, resetForm, values, meta } = form
|
||
|
||
// Helper function to get field values safely
|
||
const getFieldValue = (fieldName: string) => {
|
||
return fieldName.split('.').reduce((obj, key) => obj?.[key], values)
|
||
}
|
||
|
||
// Form validation computed
|
||
const isFormValid = computed(() => meta.value.valid)
|
||
|
||
// Form submit handler
|
||
const onSubmit = form.handleSubmit(async (values) => {
|
||
console.log('Form submitted with values:', values)
|
||
await createStall(values)
|
||
})
|
||
|
||
// Computed properties
|
||
const userHasMerchantProfile = computed(() => {
|
||
// Use the actual API response to determine if user has merchant profile
|
||
return merchantProfile.value !== null
|
||
})
|
||
|
||
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
|
||
})
|
||
|
||
|
||
const incomingOrders = computed(() => {
|
||
// Filter orders to only show those where the current user is the seller
|
||
const currentUserPubkey = auth.currentUser?.value?.pubkey
|
||
if (!currentUserPubkey) return []
|
||
|
||
return Object.values(marketStore.orders)
|
||
.filter(order => order.sellerPubkey === currentUserPubkey && order.status === 'pending')
|
||
.sort((a, b) => b.createdAt - a.createdAt)
|
||
})
|
||
|
||
const storeStats = computed(() => {
|
||
const currentUserPubkey = auth.currentUser?.value?.pubkey
|
||
if (!currentUserPubkey) {
|
||
return {
|
||
incomingOrders: 0,
|
||
pendingOrders: 0,
|
||
paidOrders: 0,
|
||
totalSales: 0,
|
||
totalProducts: 0,
|
||
activeProducts: 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),
|
||
totalProducts: 0, // TODO: Implement product management
|
||
activeProducts: 0, // TODO: Implement product management
|
||
satisfaction: userHasStalls.value ? 95 : 0, // TODO: Implement review system
|
||
totalReviews: 0 // TODO: Implement review system
|
||
}
|
||
})
|
||
|
||
// Methods
|
||
const formatDate = (timestamp: number) => {
|
||
return new Date(timestamp * 1000).toLocaleDateString('en-US', {
|
||
year: 'numeric',
|
||
month: 'short',
|
||
day: 'numeric',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
})
|
||
}
|
||
|
||
const formatStatus = (status: OrderStatus) => {
|
||
const statusMap: Record<OrderStatus, string> = {
|
||
pending: 'Pending',
|
||
paid: 'Paid',
|
||
processing: 'Processing',
|
||
shipped: 'Shipped',
|
||
delivered: 'Delivered',
|
||
cancelled: 'Cancelled'
|
||
}
|
||
return statusMap[status] || status
|
||
}
|
||
|
||
const getStatusVariant = (status: OrderStatus) => {
|
||
const variantMap: Record<OrderStatus, 'default' | 'secondary' | 'outline' | 'destructive'> = {
|
||
pending: 'outline',
|
||
paid: 'secondary',
|
||
processing: 'secondary',
|
||
shipped: 'default',
|
||
delivered: 'default',
|
||
cancelled: 'destructive'
|
||
}
|
||
return variantMap[status] || 'outline'
|
||
}
|
||
|
||
const formatPrice = (price: number, currency: string) => {
|
||
return marketStore.formatPrice(price, currency)
|
||
}
|
||
|
||
const generateInvoice = async (orderId: string) => {
|
||
console.log('Generating invoice for order:', orderId)
|
||
isGeneratingInvoice.value = orderId
|
||
|
||
try {
|
||
// Get the order from the store
|
||
const order = marketStore.orders[orderId]
|
||
if (!order) {
|
||
throw new Error('Order not found')
|
||
}
|
||
|
||
// Temporary fix: If buyerPubkey is missing, try to get it from auth
|
||
if (!order.buyerPubkey && auth.currentUser?.value?.pubkey) {
|
||
console.log('Fixing missing buyerPubkey for existing order')
|
||
marketStore.updateOrder(order.id, { buyerPubkey: auth.currentUser.value.pubkey })
|
||
}
|
||
|
||
// Temporary fix: If sellerPubkey is missing, use current user's pubkey
|
||
if (!order.sellerPubkey && auth.currentUser?.value?.pubkey) {
|
||
console.log('Fixing missing sellerPubkey for existing order')
|
||
marketStore.updateOrder(order.id, { sellerPubkey: auth.currentUser.value.pubkey })
|
||
}
|
||
|
||
// Get the updated order
|
||
const updatedOrder = marketStore.orders[orderId]
|
||
|
||
console.log('Order details for invoice generation:', {
|
||
orderId: updatedOrder.id,
|
||
orderFields: Object.keys(updatedOrder),
|
||
buyerPubkey: updatedOrder.buyerPubkey,
|
||
sellerPubkey: updatedOrder.sellerPubkey,
|
||
status: updatedOrder.status,
|
||
total: updatedOrder.total
|
||
})
|
||
|
||
// Get the user's wallet list
|
||
const userWallets = auth.currentUser?.value?.wallets || []
|
||
console.log('Available wallets:', userWallets)
|
||
|
||
if (userWallets.length === 0) {
|
||
throw new Error('No wallet available to generate invoice. Please ensure you have at least one wallet configured.')
|
||
}
|
||
|
||
// Use the first available wallet for invoice generation
|
||
const walletId = userWallets[0].id
|
||
const walletName = userWallets[0].name
|
||
const adminKey = userWallets[0].adminkey
|
||
console.log('Using wallet for invoice generation:', { walletId, walletName, balance: userWallets[0].balance_msat })
|
||
|
||
const invoice = await marketStore.createLightningInvoice(orderId, adminKey)
|
||
|
||
if (invoice) {
|
||
console.log('Lightning invoice created:', invoice)
|
||
|
||
// Send the invoice to the customer via Nostr
|
||
await sendInvoiceToCustomer(updatedOrder, invoice)
|
||
|
||
console.log('Invoice sent to customer successfully')
|
||
|
||
// Show success message (you could add a toast notification here)
|
||
alert(`Invoice generated successfully using wallet: ${walletName}`)
|
||
} else {
|
||
throw new Error('Failed to create Lightning invoice')
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to generate invoice:', error)
|
||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
|
||
|
||
// Show error message to user
|
||
alert(`Failed to generate invoice: ${errorMessage}`)
|
||
} finally {
|
||
isGeneratingInvoice.value = null
|
||
}
|
||
}
|
||
|
||
const sendInvoiceToCustomer = async (order: any, invoice: any) => {
|
||
try {
|
||
console.log('Sending invoice to customer for order:', {
|
||
orderId: order.id,
|
||
buyerPubkey: order.buyerPubkey,
|
||
sellerPubkey: order.sellerPubkey,
|
||
invoiceFields: Object.keys(invoice)
|
||
})
|
||
|
||
// Check if we have the buyer's public key
|
||
if (!order.buyerPubkey) {
|
||
console.error('Missing buyerPubkey in order:', order)
|
||
throw new Error('Cannot send invoice: buyer public key not found')
|
||
}
|
||
|
||
// Update the order with the invoice details
|
||
const updatedOrder = {
|
||
...order,
|
||
lightningInvoice: invoice,
|
||
paymentHash: invoice.payment_hash,
|
||
paymentStatus: 'pending',
|
||
paymentRequest: invoice.bolt11, // Use bolt11 field from LNBits response
|
||
updatedAt: Math.floor(Date.now() / 1000)
|
||
}
|
||
|
||
// Update the order in the store
|
||
marketStore.updateOrder(order.id, updatedOrder)
|
||
|
||
// Send the updated order to the customer via Nostr
|
||
// This will include the invoice information
|
||
await nostrmarketService.publishOrder(updatedOrder, order.buyerPubkey)
|
||
|
||
console.log('Updated order with invoice sent via Nostr to customer:', order.buyerPubkey)
|
||
} catch (error) {
|
||
console.error('Failed to send invoice to customer:', error)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
const viewOrderDetails = (orderId: string) => {
|
||
// TODO: Navigate to detailed order view
|
||
console.log('Viewing order details:', orderId)
|
||
}
|
||
|
||
const processOrder = (orderId: string) => {
|
||
// TODO: Implement order processing
|
||
console.log('Processing order:', orderId)
|
||
}
|
||
|
||
const addProduct = () => {
|
||
// TODO: Navigate to add product form
|
||
console.log('Adding new product')
|
||
}
|
||
|
||
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] // Use first wallet
|
||
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
|
||
merchantCreateError.value = null
|
||
|
||
try {
|
||
console.log('Creating merchant profile...', {
|
||
walletId: wallet.id,
|
||
adminKeyLength: wallet.adminkey.length,
|
||
adminKeyPreview: wallet.adminkey.substring(0, 8) + '...'
|
||
})
|
||
|
||
// Create merchant with empty config, exactly like the nostrmarket extension
|
||
const merchantData = {
|
||
config: {}
|
||
}
|
||
|
||
const newMerchant = await nostrmarketAPI.createMerchant(wallet.adminkey, merchantData)
|
||
|
||
console.log('Merchant profile created successfully:', {
|
||
merchantId: newMerchant.id,
|
||
publicKey: newMerchant.public_key
|
||
})
|
||
|
||
// Update local state
|
||
merchantProfile.value = newMerchant
|
||
|
||
// Show success message
|
||
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)
|
||
merchantCreateError.value = errorMessage
|
||
|
||
// Show error to user
|
||
toast.error(`Failed to create merchant profile: ${errorMessage}`)
|
||
} finally {
|
||
isCreatingMerchant.value = false
|
||
}
|
||
}
|
||
|
||
const initializeStallCreation = async () => {
|
||
const currentUser = auth.currentUser?.value
|
||
if (!currentUser?.wallets?.length) {
|
||
toast.error('No wallets available')
|
||
return
|
||
}
|
||
|
||
// Set default wallet
|
||
setFieldValue('wallet', currentUser.wallets[0].id)
|
||
|
||
// Load currencies and zones
|
||
await Promise.all([
|
||
loadAvailableCurrencies(),
|
||
loadAvailableZones()
|
||
])
|
||
|
||
showStallDialog.value = true
|
||
}
|
||
|
||
const loadAvailableCurrencies = async () => {
|
||
try {
|
||
const currencies = await nostrmarketAPI.getCurrencies()
|
||
availableCurrencies.value = currencies
|
||
} catch (error) {
|
||
console.error('Failed to load currencies:', error)
|
||
// Keep default 'sat'
|
||
}
|
||
}
|
||
|
||
const loadAvailableZones = async () => {
|
||
const currentUser = auth.currentUser?.value
|
||
if (!currentUser?.wallets?.length) return
|
||
|
||
try {
|
||
const zones = await nostrmarketAPI.getZones(currentUser.wallets[0].inkey)
|
||
availableZones.value = zones
|
||
} catch (error) {
|
||
console.error('Failed to load zones:', error)
|
||
}
|
||
}
|
||
|
||
const loadStallsList = async () => {
|
||
const currentUser = auth.currentUser?.value
|
||
if (!currentUser?.wallets?.length) return
|
||
|
||
try {
|
||
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)
|
||
} catch (error) {
|
||
console.error('Failed to load stalls:', error)
|
||
}
|
||
}
|
||
|
||
const addNewZone = async () => {
|
||
const currentUser = auth.currentUser?.value
|
||
if (!currentUser?.wallets?.length) {
|
||
toast.error('No wallets available')
|
||
return
|
||
}
|
||
|
||
const newZone = getFieldValue('newZone')
|
||
|
||
if (!newZone?.name || !newZone.selectedCountries?.length || (newZone.cost ?? -1) < 0) {
|
||
toast.error('Please fill in all zone details')
|
||
return
|
||
}
|
||
|
||
try {
|
||
const createdZone = await nostrmarketAPI.createZone(
|
||
currentUser.wallets[0].adminkey,
|
||
{
|
||
name: newZone.name,
|
||
currency: getFieldValue('currency'),
|
||
cost: newZone.cost,
|
||
countries: newZone.selectedCountries
|
||
}
|
||
)
|
||
|
||
// Add to available zones and select it
|
||
availableZones.value.push(createdZone)
|
||
const currentSelectedZones = getFieldValue('selectedZones') || []
|
||
setFieldValue('selectedZones', [...currentSelectedZones, createdZone.id])
|
||
|
||
// Reset the new zone form
|
||
setFieldValue('newZone', {
|
||
name: '',
|
||
cost: 0,
|
||
selectedCountries: []
|
||
})
|
||
|
||
toast.success(`Zone "${newZone.name}" created successfully`)
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : 'Failed to create zone'
|
||
toast.error(`Failed to create zone: ${errorMessage}`)
|
||
}
|
||
}
|
||
|
||
|
||
const createStall = async (formData: any) => {
|
||
const currentUser = auth.currentUser?.value
|
||
if (!currentUser?.wallets?.length) {
|
||
toast.error('No wallets available')
|
||
return
|
||
}
|
||
|
||
const { name, description, currency, selectedZones } = formData
|
||
|
||
isCreatingStall.value = true
|
||
stallCreateError.value = null
|
||
|
||
try {
|
||
// Get the selected zones data
|
||
const selectedZoneData = availableZones.value.filter(zone =>
|
||
selectedZones.includes(zone.id)
|
||
)
|
||
|
||
const stallData: CreateStallRequest = {
|
||
name,
|
||
wallet: currentUser.wallets[0].id,
|
||
currency,
|
||
shipping_zones: selectedZoneData,
|
||
config: {
|
||
description: description || undefined
|
||
}
|
||
}
|
||
|
||
console.log('Creating stall:', {
|
||
name,
|
||
currency,
|
||
zonesCount: selectedZoneData.length
|
||
})
|
||
|
||
const newStall = await nostrmarketAPI.createStall(
|
||
currentUser.wallets[0].adminkey,
|
||
stallData
|
||
)
|
||
|
||
console.log('Stall created successfully:', {
|
||
stallId: newStall.id,
|
||
stallName: newStall.name
|
||
})
|
||
|
||
// Update stalls list
|
||
await loadStallsList()
|
||
|
||
// Reset form and close dialog
|
||
resetForm({
|
||
values: {
|
||
name: '',
|
||
description: '',
|
||
currency: 'sat',
|
||
wallet: currentUser.wallets[0].id,
|
||
selectedZones: [],
|
||
newZone: {
|
||
name: '',
|
||
cost: 0,
|
||
selectedCountries: []
|
||
}
|
||
}
|
||
})
|
||
showStallDialog.value = false
|
||
|
||
toast.success('Store created successfully! You can now add products.')
|
||
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : 'Failed to create store'
|
||
console.error('Error creating stall:', error)
|
||
stallCreateError.value = errorMessage
|
||
toast.error(`Failed to create store: ${errorMessage}`)
|
||
} finally {
|
||
isCreatingStall.value = false
|
||
}
|
||
}
|
||
|
||
const navigateToMarket = () => router.push('/market')
|
||
const viewAllOrders = () => router.push('/market-dashboard?tab=orders')
|
||
const generateBulkInvoices = () => console.log('Generate bulk invoices')
|
||
const exportOrders = () => console.log('Export orders')
|
||
const manageProducts = () => console.log('Manage products')
|
||
const storeSettings = () => router.push('/market-dashboard?tab=settings')
|
||
const analytics = () => console.log('View analytics')
|
||
|
||
const getFirstWalletName = () => {
|
||
const userWallets = auth.currentUser?.value?.wallets || []
|
||
if (userWallets.length > 0) {
|
||
return userWallets[0].name
|
||
}
|
||
return 'N/A'
|
||
}
|
||
|
||
// Methods
|
||
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] // Use first wallet
|
||
if (!wallet.inkey) {
|
||
console.warn('Wallet missing invoice key for merchant check')
|
||
return
|
||
}
|
||
|
||
isLoadingMerchant.value = true
|
||
merchantCheckError.value = null
|
||
|
||
try {
|
||
console.log('Checking for merchant profile...')
|
||
const merchant = await nostrmarketAPI.getMerchant(wallet.inkey)
|
||
merchantProfile.value = merchant
|
||
|
||
console.log('Merchant profile check result:', {
|
||
hasMerchant: !!merchant,
|
||
merchantId: merchant?.id,
|
||
active: merchant?.config?.active
|
||
})
|
||
} 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
|
||
}
|
||
}
|
||
|
||
// Lifecycle
|
||
onMounted(async () => {
|
||
console.log('Merchant Store component loaded')
|
||
await checkMerchantProfile()
|
||
})
|
||
|
||
// 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()
|
||
}
|
||
})
|
||
</script>
|
||
|