feat: Add market integration roadmap to NOSTR architecture documentation

- Introduce a comprehensive roadmap for integrating nostr-market-app purchasing functionality into the web-app.
- Outline key components of the shopping cart system, checkout process, and order management.
- Detail phased implementation strategy, including enhanced user experience and advanced features.
- Include security, performance, and testing considerations to ensure robust integration.

feat: Enhance market store with new order and cart management features

- Introduce new interfaces for Order, OrderItem, ContactInfo, and ShippingZone to support enhanced order management.
- Update Stall and Product interfaces to include currency and shipping details.
- Implement a comprehensive shopping cart system with stall-specific carts, including methods for adding, removing, and updating items.
- Add payment-related interfaces and methods for managing payment requests and statuses.
- Enhance filter options to include in-stock status and payment methods, improving product filtering capabilities.
- Refactor computed properties and methods for better cart management and checkout processes.

feat: Implement shopping cart functionality with new components and routing

- Add ShoppingCart, CartItem, and CartSummary components to manage cart items and display summaries.
- Introduce Cart.vue page to serve as the main shopping cart interface, integrating cart and summary components.
- Update Navbar.vue to include a cart icon with item count, enhancing user navigation.
- Implement cart management features in the market store, including item addition, quantity updates, and removal.
- Establish routing for the cart page, ensuring seamless navigation for users.
- Enhance ProductCard.vue to support adding items to the cart directly from the product listing.

feat: Update cart and checkout functionality with improved navigation and button labels

- Change "Proceed to Checkout" button text to dynamic "Place Order" based on context in CartSummary.vue.
- Update "Continue Shopping" button to "Back to Cart" in CartSummary.vue for clearer navigation.
- Modify routing for checkout to include stall ID in ShoppingCart.vue, enhancing checkout process.
- Simplify Cart.vue by removing CartSummary component and focusing on ShoppingCart display.
- Add new route for checkout with stall ID in router configuration for better handling of checkout flows.

feat: Enhance cart and checkout components with improved shipping address handling

- Update CartSummary.vue to use readonly types for cart items and shipping zones, ensuring immutability.
- Modify Checkout.vue to conditionally display the shipping address field based on the selected shipping zone's requirements for physical shipping.
- Add a digital delivery note for products that do not require a shipping address.
- Introduce a computed property to determine if a shipping address is required, improving validation logic during checkout.
- Update market store to include a new property for shipping zones indicating if physical shipping is required.

feat: Implement order placement functionality in checkout process

- Add a "Place Order" button in Checkout.vue that triggers the order placement process.
- Introduce loading state during order placement to enhance user experience.
- Implement createAndPlaceOrder method in market store to handle order creation and status updates.
- Include error handling for order placement failures, providing user feedback on errors.
- Update checkout logic to validate shipping zone and contact information before proceeding.

feat: Add Order History page and update Navbar for order tracking

- Introduce a new OrderHistory.vue page to display users' past orders with filtering and sorting options.
- Update Navbar.vue to include an "Order History" option with a badge showing the count of orders.
- Implement computed properties for order count and enhance user navigation experience.

feat: Integrate Nostr functionality for order management and user notifications

- Add NostrExtensionGuide component to inform users about the required Nostr extension for order transmission.
- Implement useNostrOrders composable to manage Nostr connection, event creation, and order sending.
- Update Checkout.vue to display Nostr connection status and provide feedback on order transmission.
- Enhance OrderHistory.vue to show Nostr transmission status and details for each order.
- Modify market store to handle Nostr event details and errors during order placement, ensuring local fallback.
- Introduce types for Nostr events to improve type safety and integration with the existing order management system.

refactor: Update Nostr relay configuration to use environment variable

- Change DEFAULT_RELAYS to dynamically retrieve relay URLs from the VITE_MARKET_RELAYS environment variable.
- Add error handling to ensure relays are configured before establishing a connection.
- Modify createBlankEvent function to return a more precise type.
- Update event signing process to ensure the event ID is generated correctly before signing.

refactor: useAuth switch Enhance Nostr order management with authentication checks

- Integrate user authentication checks to ensure Nostr features are only accessible to authenticated users.
- Replace direct window.nostr calls with auth store methods for retrieving public and private keys.
- Implement a helper function for signing events and mock encryption for order content.
- Remove obsolete Nostr type definitions to streamline the codebase.

feat: Enhance Checkout.vue with Nostr processing feedback and cleanup

- Update the checkout button to disable based on order placement state.
- Simplify order placement feedback by removing unnecessary Nostr processing checks.
- Introduce a new visual indicator for Nostr order processing status.
- Refactor computed properties for better clarity and efficiency in shipping zone handling.

refactor: Streamline Nostr order handling and integrate buyer public key retrieval

- Remove redundant Nostr relay tag from order event creation in useNostrOrders.
- Update Checkout.vue to retrieve the buyer's public key from the auth store, enhancing order placement logic.
- Modify createAndPlaceOrder method in market store to accept an optional Nostr orders instance for improved flexibility in order processing.

refactor: Remove Nostr-related components and streamline order processing

- Delete NostrExtensionGuide.vue and associated type definitions to simplify the codebase.
- Remove unused useNostr.ts file and related logic from useNostrOrders.ts.
- Update order handling in market store to directly integrate Nostr publishing without relying on external components.
- Enhance Checkout.vue and Cart.vue to reflect changes in Nostr integration and provide clearer order status feedback.

feat: Enhance Nostr chat functionality with malformed message handling

- Introduce tracking for malformed message IDs to prevent repeated processing attempts.
- Implement functions to mark messages as malformed, clean up old entries, and retrieve statistics on malformed messages.
- Add periodic cleanup of malformed messages to manage memory usage.
- Enhance message processing logic to skip previously identified malformed messages and provide detailed error handling for decryption failures.
- Update the return object to include new functions for managing malformed messages.

ZZ feat: Implement Lightning invoice management in market store

- Add functionality to create and manage Lightning invoices for orders.
- Introduce payment monitoring and status updates for invoices.
- Implement payment confirmation messaging via Nostr upon successful payment.
- Enhance order interface to include new fields for Lightning invoice details and payment status.

ZZ feat: Enhance OrderHistory.vue with payment status indicators and invoice management

- Add visual indicators for payment status, including 'Paid' and
'Payment Pending' badges.
- Implement expandable payment display for orders with Lightning
invoices.
- Introduce functionality to toggle payment display and generate
Lightning invoices.
- Update order status messaging to reflect payment requirements and
invoice generation status.

ZZ feat: Enhance OrderHistory.vue with payment status indicators and invoice management

- Add visual indicators for payment status, including 'Paid' and
'Payment Pending' badges.
- Implement expandable payment display for orders with Lightning
invoices.
- Introduce functionality to toggle payment display and generate
Lightning invoices.
- Update order status messaging to reflect payment requirements and
invoice generation status.

feat: Implement order event handling in useOrderEvents composable

- Introduce useOrderEvents composable to manage subscription and processing of order-related events.
- Define order event types and interfaces for better type safety and clarity.
- Implement methods to handle payment requests, order status updates, and invoice generation.
- Enhance OrderHistory.vue to display order event subscription status and last update timestamp.
- Update market store to include order update functionality for better integration with order events.

FIX: Build errors

refactor: Update component styles and improve UI consistency across market pages

- Replace various color classes with updated design tokens for better consistency.
- Change background colors of components to align with the new design system.
- Update text colors to enhance readability and maintain a cohesive look.
- Refactor class names in CartItem.vue, CartSummary.vue, DashboardOverview.vue, and other components to use the new color scheme.
- Ensure all components reflect the updated design guidelines for a unified user experience.

refactor: Remove Order History references from Navbar component

- Eliminate order count computation and related UI elements from the Navbar.
- Streamline the Navbar by removing the Order History button and badge.
- Maintain existing functionality for other menu items, ensuring a cleaner user interface.

feat: Implement QR code generation and download functionality in PaymentDisplay component

- Add QR code generation for payment requests using the qrcode library.
- Enhance UI to display loading states and error messages during QR code generation.
- Introduce a download button for users to save the generated QR code.
- Implement logic to regenerate QR code when the invoice changes.

refactor: Replace useRelayHub with relayHubComposable across components

- Update imports in multiple components and composables to use the new relayHubComposable for better consistency and maintainability.
- Enhance OrderHistory.vue with debug information for development, displaying key states related to orders, authentication, and relay hub connectivity.
- Remove unnecessary reconnect button from RelayHubStatus.vue to streamline user interactions.
- Improve logging in useOrderEvents for better debugging and monitoring of order event subscriptions.

refactor: Update OrderHistory.vue styles for improved UI consistency

- Replace color classes with updated design tokens for better alignment with the new design system.
- Enhance readability by adjusting text colors and background styles for payment status indicators.
- Ensure a cohesive look across the component by standardizing class names and styles.

refactor: Update component styles for improved UI consistency across checkout pages

- Replace color classes with updated design tokens for better alignment with the new design system.
- Enhance readability by adjusting text colors and background styles in CartSummary.vue, PaymentDisplay.vue, Checkout.vue, and OrderHistory.vue.
- Standardize class names and styles to ensure a cohesive look across all components.

feat: Implement invoice generation and Nostr integration in MerchantStore component

- Add functionality to generate Lightning invoices for orders and send them to customers via Nostr.
- Introduce a new sendInvoiceToCustomer method to update order details and publish invoice information.
- Enhance order event handling in useOrderEvents to update existing orders with new invoice data.
- Improve error handling and logging for invoice generation and sending processes.

feat: Enhance MerchantStore and PaymentDisplay components for improved invoice handling

- Add wallet indicator in MerchantStore to display the selected wallet name during pending orders.
- Implement temporary fixes for missing buyer and seller public keys when generating invoices.
- Update invoice generation logic to utilize the first available wallet and improve error handling.
- Modify PaymentDisplay to use the new bolt11 field for payment requests and enhance date formatting.
- Refactor order event handling to ensure accurate updates and invoice management across components.

feat: Enhance order event processing in useOrderEvents composable

- Refactor processOrderEvent to handle incoming Nostr market order events with improved validation and logging.
- Implement logic to update existing orders or create new ones based on event data, ensuring accurate order management.
- Add detailed console logging for better debugging and tracking of order events and their statuses.
- Ensure compatibility with market order structure and invoice details for seamless integration with payment processing.

feat: Enhance order management with localStorage persistence

- Update createOrder method to optionally accept an order ID from events, improving order tracking.
- Convert items from readonly to mutable for better manipulation.
- Implement localStorage persistence for orders, ensuring data is saved and loaded across sessions.
- Add methods to save and load orders from localStorage, enhancing user experience and data reliability.

feat: Update invoice creation to support additional metadata and nostrmarket compatibility

- Modify createInvoice method to accept an optional extra parameter for additional metadata.
- Change invoice tag to 'nostrmarket' for improved compatibility with Nostr market.
- Include merchant and buyer public keys in the invoice data for better integration.
- Update invoice creation in market store to utilize new parameters for enhanced functionality.

feat: Enhance order and invoice handling for Nostr market compatibility

- Add originalOrderId to order events for tracking Nostr order IDs.
- Update invoice creation to utilize original Nostr order ID when generating invoices.
- Improve logging for invoice requests to LNBits, providing better visibility into the data being sent.
- Ensure compatibility with nostrmarket by adjusting order ID handling in the market store.

fix: Refine invoice creation logic for Nostr market compatibility

- Adjust order ID handling in invoice creation to prioritize originalOrderId for better compatibility with nostrmarket.
- Enhance logging to provide clearer insights into the order ID being used during invoice generation.

feat: Integrate nostrmarket service for order publishing and merchant catalog management

- Implement functionality to publish orders via the nostrmarket protocol, replacing the previous Nostr integration.
- Add methods to publish merchant catalogs, including stalls and products, to nostrmarket with event ID tracking.
- Enhance order interface to include nostrEventId for better integration with nostrmarket.
- Improve error handling and logging for nostrmarket publishing processes.

refactor: Simplify order creation logic in useOrderEvents and update contact structure in nostrmarketService

- Streamline order creation by using event.id and defaulting to 'unknown' for stallId.
- Update contact structure to include address and message, removing optional email and phone fields for clarity.
- Ensure compatibility with new order data structure for improved integration with nostrmarket.

feat: Add bech32 to hex conversion utility and integrate into nostrmarketService

- Implement a new utility function to convert bech32 keys to hex format, enhancing key handling.
- Update nostrmarketService to utilize the new conversion function for user public and private keys.
- Modify contact structure to include additional fields for improved order information management.

feat: Add nostrclient configuration to AppConfig for enhanced Nostr integration

- Introduce a new nostrclient property in AppConfig to manage Nostr client settings.
- Include url and enabled fields to configure the Nostr client connection dynamically.
- Ensure compatibility with environment variables for flexible deployment configurations.

feat: Introduce comprehensive order management and fulfillment documentation

- Add ORDER_MANAGEMENT_FULFILLMENT.md to detail the complete order lifecycle, including order states, data models, and merchant/customer interfaces.
- Implement test scripts for verifying order and payment request formats in test-nostrmarket-format.js.
- Create PaymentRequestDialog.vue for handling payment requests with dynamic options and QR code generation.
- Enhance useOrderEvents.ts to process nostrmarket protocol messages for order management.
- Update nostrmarketService.ts to handle payment requests and order status updates, ensuring seamless integration with the marketplace.
- Integrate payment request dialog in Market.vue and manage its state in the market store.

refactor: Remove obsolete test script for nostrmarket order format

- Delete test-nostrmarket-format.js as it is no longer needed for verifying order and payment request formats.
- Update PaymentRequestDialog.vue to enhance UI components and integrate QR code generation for payment requests.
- Refactor payment handling and notification logic to utilize toast notifications instead of Quasar's notify system.

feat: Enhance OrderHistory component with payment request handling and QR code generation

- Add UI elements to display payment request status and options in OrderHistory.vue.
- Implement functions to copy payment requests, open Lightning wallets, and download QR codes.
- Update nostrmarketService to generate QR codes for payment requests and manage order statuses effectively.
- Remove obsolete PaymentRequestDialog integration from Market.vue for a cleaner UI.

feat: Add debug information and toast notifications in OrderHistory component

- Introduce debug info display for payment requests and hashes in OrderHistory.vue.
- Implement toast notifications for actions like copying payment requests, opening wallets, and downloading QR codes.
- Enhance error handling with user feedback for various order-related actions.
- Remove obsolete payment request dialog methods from market store for cleaner code.

feat: Revamp CartItem and ShoppingCart components for improved layout and functionality

- Enhance CartItem.vue with responsive design for desktop and mobile views, including better organization of product details, price, quantity controls, and remove button.
- Update ShoppingCart.vue to separate desktop and mobile layouts, improving the user experience with clearer action buttons and cart summary display.
- Implement consistent styling and layout adjustments for better visual coherence across different screen sizes.
This commit is contained in:
padreug 2025-08-13 15:31:18 +02:00
parent 93ffb8bf32
commit ea5a2380f1
43 changed files with 8983 additions and 146 deletions

68
src/pages/Cart.vue Normal file
View file

@ -0,0 +1,68 @@
<template>
<div class="container mx-auto px-4 py-8">
<!-- Success Message -->
<div v-if="orderSuccess" class="mb-8">
<div class="bg-green-500/10 border border-green-200 rounded-lg p-6">
<div class="flex items-center space-x-3">
<div class="w-8 h-8 bg-green-500/20 rounded-full flex items-center justify-center">
<CheckCircle class="w-5 h-5 text-green-600" />
</div>
<div>
<h3 class="text-lg font-semibold text-green-900">Order Placed Successfully!</h3>
<p class="text-green-700">
Your order has been placed and sent to the merchant.
<span v-if="orderId" class="font-medium">Order ID: {{ orderId }}</span>
</p>
<!-- Nostr Status -->
<div v-if="orderId && marketStore.orders[orderId]" class="mt-2">
<div v-if="marketStore.orders[orderId].sentViaNostr" class="flex items-center gap-2 text-sm text-green-600">
<div class="w-2 h-2 bg-green-500 rounded-full"></div>
<span> Sent via Nostr network</span>
</div>
<div v-else class="flex items-center gap-2 text-sm text-yellow-600">
<div class="w-2 h-2 bg-yellow-500 rounded-full"></div>
<span> Stored locally only</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Page Header -->
<div class="mb-8">
<h1 class="text-3xl font-bold text-foreground">Shopping Cart</h1>
<p class="text-muted-foreground mt-2">
Review your items and proceed to checkout for each stall
</p>
</div>
<!-- Cart Content -->
<div class="max-w-4xl mx-auto">
<ShoppingCart />
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { useMarketStore } from '@/stores/market'
import { CheckCircle } from 'lucide-vue-next'
import ShoppingCart from '@/components/market/ShoppingCart.vue'
const route = useRoute()
const marketStore = useMarketStore()
// Check for order success from query params
const orderSuccess = computed(() => route.query.orderSuccess === 'true')
const orderId = computed(() => route.query.orderId as string)
// Set the first cart as active if none is selected (for navigation purposes)
onMounted(() => {
if (marketStore.allStallCarts.length > 0 && !marketStore.activeStallCart) {
const firstCart = marketStore.allStallCarts[0]
marketStore.setCheckoutCart(firstCart.id)
}
})
</script>

438
src/pages/Checkout.vue Normal file
View file

@ -0,0 +1,438 @@
<template>
<div class="container mx-auto px-4 py-8">
<!-- Loading State -->
<div v-if="!isReady" class="flex justify-center items-center min-h-64">
<div class="flex flex-col items-center space-y-4">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
<p class="text-muted-foreground">Loading checkout...</p>
</div>
</div>
<!-- Error State -->
<div v-else-if="error" class="text-center py-12">
<h2 class="text-2xl font-bold text-red-600 mb-4">Checkout Error</h2>
<p class="text-muted-foreground mb-4">{{ error }}</p>
<Button @click="$router.push('/cart')" variant="outline">
Back to Cart
</Button>
</div>
<!-- Checkout Content -->
<div v-else-if="checkoutCart && checkoutStall" class="max-w-4xl mx-auto">
<!-- Page Header -->
<div class="mb-8">
<div class="flex items-center justify-between">
<div>
<h1 class="text-3xl font-bold text-foreground">Checkout</h1>
<p class="text-muted-foreground mt-2">
Complete your purchase from {{ checkoutStall.name }}
</p>
</div>
<Button @click="$router.push('/cart')" variant="outline">
Back to Cart
</Button>
</div>
</div>
<!-- Checkout Grid -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Main Checkout Form -->
<div class="lg:col-span-2 space-y-6">
<!-- Stall Information -->
<div class="bg-card border rounded-lg p-6">
<div class="flex items-center space-x-4 mb-4">
<div class="w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center">
<Store class="w-6 h-6 text-primary" />
</div>
<div>
<h3 class="text-lg font-semibold text-foreground">{{ checkoutStall.name }}</h3>
<p v-if="checkoutStall.description" class="text-muted-foreground">
{{ checkoutStall.description }}
</p>
</div>
</div>
</div>
<!-- Contact Information -->
<div class="bg-card border rounded-lg p-6">
<h3 class="text-lg font-semibold text-foreground mb-4">Contact Information</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-foreground mb-2">
Email (optional)
</label>
<Input
v-model="contactInfo.email"
type="email"
placeholder="your@email.com"
class="w-full"
/>
</div>
<div>
<label class="block text-sm font-medium text-foreground mb-2">
Message to Merchant (optional)
</label>
<textarea
v-model="contactInfo.message"
rows="3"
placeholder="Any special requests or notes for the merchant..."
class="w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring bg-background text-foreground"
></textarea>
</div>
</div>
</div>
<!-- Shipping Information -->
<div class="bg-card border rounded-lg p-6">
<h3 class="text-lg font-semibold text-foreground mb-4">Shipping Information</h3>
<!-- Shipping Zone Selection -->
<div class="mb-4">
<label class="block text-sm font-medium text-foreground mb-2">
Shipping Zone
</label>
<div v-if="availableShippingZones.length > 0" class="space-y-2">
<div
v-for="zone in availableShippingZones"
:key="zone.id"
@click="selectShippingZone(zone)"
class="flex items-center justify-between p-3 border rounded cursor-pointer hover:bg-muted/50"
:class="{ 'border-primary bg-primary/10': selectedShippingZone?.id === zone.id }"
>
<div>
<p class="font-medium text-foreground">{{ zone.name }}</p>
<p v-if="zone.description" class="text-sm text-muted-foreground">
{{ zone.description }}
</p>
<p v-if="zone.estimatedDays" class="text-xs text-muted-foreground">
Estimated: {{ zone.estimatedDays }}
</p>
</div>
<p class="font-semibold text-foreground">
{{ formatPrice(zone.cost, zone.currency) }}
</p>
</div>
</div>
<p v-else class="text-sm text-muted-foreground">
No shipping zones available for this stall.
</p>
</div>
<!-- Shipping Address (only show for physical shipping) -->
<div v-if="selectedShippingZone && requiresPhysicalShipping" class="mt-4">
<label class="block text-sm font-medium text-foreground mb-2">
Shipping Address <span class="text-red-500">*</span>
</label>
<textarea
v-model="contactInfo.address"
rows="3"
placeholder="Enter your shipping address..."
class="w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring bg-background text-foreground"
required
></textarea>
<p class="text-sm text-muted-foreground mt-1">
Required for physical product delivery
</p>
</div>
<!-- Digital Delivery Note -->
<div v-if="selectedShippingZone && !requiresPhysicalShipping" class="mt-4 p-3 bg-muted/50 border border-border rounded-md">
<div class="flex items-center space-x-2">
<div class="w-5 h-5 text-muted-foreground">📧</div>
<div class="text-sm text-muted-foreground">
<p class="font-medium text-foreground">Digital Delivery</p>
<p>This product will be delivered digitally. No shipping address required.</p>
</div>
</div>
</div>
</div>
<!-- Payment Method -->
<div class="bg-card border rounded-lg p-6">
<h3 class="text-lg font-semibold text-foreground mb-4">Payment Method</h3>
<div class="space-y-3">
<div class="flex items-center space-x-3">
<input
type="radio"
id="lightning"
v-model="paymentMethod"
value="lightning"
class="text-primary focus:ring-primary"
/>
<label for="lightning" class="text-sm font-medium text-foreground">
Lightning Network (Recommended)
</label>
</div>
<div class="flex items-center space-x-3">
<input
type="radio"
id="btc_onchain"
v-model="paymentMethod"
value="btc_onchain"
class="text-primary focus:ring-primary"
/>
<label for="btc_onchain" class="text-sm font-medium text-foreground">
Bitcoin Onchain
</label>
</div>
</div>
</div>
<!-- Place Order Button -->
<div class="bg-card border rounded-lg p-6">
<!-- Nostr Status Indicator -->
<div class="mb-4 p-3 rounded-lg border" :class="{
'bg-green-500/10 border-green-200': nostrOrders.isReady.value,
'bg-yellow-500/10 border-yellow-200': !nostrOrders.isReady.value
}">
<div class="flex items-center gap-2 text-sm">
<div v-if="nostrOrders.isReady.value" class="flex items-center gap-2 text-green-700">
<Wifi class="w-4 h-4" />
<span>Connected to Nostr network</span>
</div>
<div v-else class="flex items-center gap-2 text-yellow-700">
<WifiOff class="w-4 h-4" />
<span>Nostr network unavailable</span>
</div>
</div>
<p v-if="!nostrOrders.isReady.value" class="text-xs text-yellow-600 mt-1">
Orders will be stored locally only. Please log in to send orders to merchants.
</p>
<!-- Test Encryption Button -->
<div v-if="nostrOrders.isReady.value" class="mt-3">
<Button
@click="testEncryption"
variant="outline"
size="sm"
:disabled="isTestingEncryption"
class="text-xs"
>
<div v-if="isTestingEncryption" class="flex items-center gap-2">
<div class="animate-spin rounded-full h-3 w-3 border-b-2 border-primary"></div>
<span>Testing...</span>
</div>
<div v-else class="flex items-center gap-2">
<span>Test NIP-04 Encryption</span>
</div>
</Button>
<p v-if="encryptionTestResult" class="text-xs mt-1" :class="{
'text-green-600': encryptionTestResult === 'success',
'text-red-600': encryptionTestResult === 'error'
}">
{{ encryptionTestResult === 'success' ? '✓ Encryption test passed' : '✗ Encryption test failed' }}
</p>
</div>
</div>
<Button
@click="handleCheckout"
:disabled="!canProceedToCheckout || isPlacingOrder"
variant="default"
class="w-full"
size="lg"
>
<div v-if="isPlacingOrder" class="flex items-center space-x-2">
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
<span>Placing Order...</span>
</div>
<div v-else class="flex items-center space-x-2">
<Lock class="w-4 h-4" />
<span>Place Order</span>
</div>
</Button>
<p v-if="error" class="text-sm text-red-600 mt-2 text-center">
{{ error }}
</p>
</div>
</div>
<!-- Order Summary Sidebar -->
<div class="lg:col-span-1">
<div class="sticky top-8">
<CartSummary
:stall-id="checkoutCart.id"
:cart-items="checkoutCart.products"
:subtotal="checkoutCart.subtotal"
:currency="checkoutCart.currency"
:available-shipping-zones="availableShippingZones"
:selected-shipping-zone="selectedShippingZone || undefined"
@shipping-zone-selected="selectShippingZone"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useMarketStore } from '@/stores/market'
import { nostrOrders } from '@/composables/useNostrOrders'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Store, Lock, Wifi, WifiOff } from 'lucide-vue-next'
import CartSummary from '@/components/market/CartSummary.vue'
import type { ShippingZone, ContactInfo } from '@/stores/market'
import { useAuth } from '@/composables/useAuth'
const route = useRoute()
const router = useRouter()
const marketStore = useMarketStore()
const auth = useAuth()
// Route parameters
const stallId = route.params.stallId as string
// Local state
const contactInfo = ref<ContactInfo>({
email: '',
message: '',
address: ''
})
const paymentMethod = ref<'lightning' | 'btc_onchain'>('lightning')
const selectedShippingZone = ref<ShippingZone | null>(null)
const error = ref<string | null>(null)
const isPlacingOrder = ref(false)
const isTestingEncryption = ref(false)
const encryptionTestResult = ref<'success' | 'error' | null>(null)
// Computed properties
const checkoutCart = computed(() => marketStore.checkoutCart)
const checkoutStall = computed(() => marketStore.checkoutStall)
const availableShippingZones = computed(() => {
if (!checkoutStall.value) return []
return checkoutStall.value.shipping || []
})
const isReady = computed(() => {
return checkoutCart.value && checkoutStall.value
})
const requiresPhysicalShipping = computed(() => {
return selectedShippingZone.value?.requiresPhysicalShipping || false
})
const canProceedToCheckout = computed(() => {
return selectedShippingZone.value && (requiresPhysicalShipping.value ? contactInfo.value.address : true)
})
// Methods
const selectShippingZone = (shippingZone: ShippingZone) => {
selectedShippingZone.value = shippingZone
if (checkoutCart.value) {
marketStore.setShippingZone(checkoutCart.value.id, shippingZone)
}
}
const handleCheckout = async () => {
// Validate required fields
if (!selectedShippingZone.value) {
error.value = 'Please select a shipping zone'
return
}
if (requiresPhysicalShipping.value && !contactInfo.value.address) {
error.value = 'Please provide a shipping address'
return
}
try {
// Clear any previous errors
error.value = null
isPlacingOrder.value = true
// Create the order
const order = await marketStore.createAndPlaceOrder({
cartId: checkoutCart.value!.id,
stallId: checkoutCart.value!.id,
buyerPubkey: auth.currentUser?.value?.pubkey || '', // Get from authenticated user
sellerPubkey: checkoutStall.value!.pubkey,
status: 'pending',
items: checkoutCart.value!.products.map(item => ({
productId: item.product.id,
productName: item.product.name,
quantity: item.quantity,
price: item.product.price,
currency: item.product.currency
})),
contactInfo: contactInfo.value,
shippingZone: selectedShippingZone.value,
paymentMethod: paymentMethod.value,
subtotal: checkoutCart.value!.subtotal,
shippingCost: selectedShippingZone.value.cost,
total: checkoutCart.value!.subtotal + selectedShippingZone.value.cost,
currency: checkoutCart.value!.currency
})
// Show success message
console.log('Order placed successfully:', order)
// TODO: Navigate to payment page or show payment modal
// For now, redirect to cart with success message
router.push({
path: '/cart',
query: { orderSuccess: 'true', orderId: order.id }
})
} catch (err) {
error.value = err instanceof Error ? err.message : 'Failed to place order'
console.error('Order placement failed:', err)
} finally {
isPlacingOrder.value = false
}
}
const testEncryption = async () => {
try {
isTestingEncryption.value = true
encryptionTestResult.value = null
const success = await nostrOrders.testEncryption()
encryptionTestResult.value = success ? 'success' : 'error'
if (success) {
console.log('NIP-04 encryption test passed!')
} else {
console.error('NIP-04 encryption test failed!')
}
} catch (error) {
console.error('Encryption test error:', error)
encryptionTestResult.value = 'error'
} finally {
isTestingEncryption.value = false
}
}
const formatPrice = (price: number, currency: string) => {
if (currency === 'sats' || currency === 'sat') {
return `${price.toLocaleString()} sats`
}
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency.toUpperCase()
}).format(price)
}
// Initialize checkout
onMounted(() => {
if (!stallId) {
error.value = 'No stall ID provided'
return
}
// Set the checkout cart for this stall
marketStore.setCheckoutCart(stallId)
// Auto-select shipping zone if only one available
if (availableShippingZones.value.length === 1) {
selectShippingZone(availableShippingZones.value[0])
}
})
</script>

View file

@ -85,10 +85,10 @@
</div>
<!-- Cart Summary -->
<div v-if="marketStore.cartItemCount > 0" class="fixed bottom-4 right-4">
<div v-if="marketStore.totalCartItems > 0" class="fixed bottom-4 right-4">
<Button @click="viewCart" class="shadow-lg">
<ShoppingCart class="w-5 h-5 mr-2" />
Cart ({{ marketStore.cartItemCount }})
Cart ({{ marketStore.totalCartItems }})
</Button>
</div>
</div>
@ -97,6 +97,7 @@
<script setup lang="ts">
import { onMounted, onUnmounted, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useMarketStore } from '@/stores/market'
import { useMarket } from '@/composables/useMarket'
import { useMarketPreloader } from '@/composables/useMarketPreloader'
@ -108,6 +109,7 @@ import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'
import { ShoppingCart } from 'lucide-vue-next'
import ProductCard from '@/components/market/ProductCard.vue'
const router = useRouter()
const marketStore = useMarketStore()
const market = useMarket()
const marketPreloader = useMarketPreloader()
@ -121,7 +123,7 @@ const needsToLoadMarket = computed(() => {
marketStore.products.length === 0
})
// Check if market data is ready (either preloaded or loaded)
// Check if market data is ready (either preloaded or loaded)
const isMarketReady = computed(() => {
const isLoading = marketStore.isLoading ?? false
const ready = marketPreloader.isPreloaded.value ||
@ -158,12 +160,12 @@ const addToCart = (product: any) => {
marketStore.addToCart(product)
}
const viewProduct = (product: any) => {
const viewProduct = (_product: any) => {
// TODO: Navigate to product detail page
}
const viewCart = () => {
// TODO: Navigate to cart page
router.push('/cart')
}
onMounted(() => {

View file

@ -0,0 +1,126 @@
<template>
<div class="container mx-auto px-4 py-8">
<!-- Page Header -->
<div class="mb-8">
<h1 class="text-3xl font-bold text-foreground">Market Dashboard</h1>
<p class="text-muted-foreground mt-2">
Manage your market activities as both a customer and merchant
</p>
</div>
<!-- Dashboard Tabs -->
<div class="mb-6">
<nav class="flex space-x-8 border-b border-border">
<button
v-for="tab in tabs"
:key="tab.id"
@click="activeTab = tab.id"
:class="[
'py-2 px-1 border-b-2 font-medium text-sm transition-colors',
activeTab === tab.id
? 'border-primary text-primary'
: 'border-transparent text-muted-foreground hover:text-foreground hover:border-muted-foreground'
]"
>
<div class="flex items-center space-x-2">
<component :is="tab.icon" class="w-4 h-4" />
<span>{{ tab.name }}</span>
<Badge v-if="tab.badge" variant="secondary" class="text-xs">
{{ tab.badge }}
</Badge>
</div>
</button>
</nav>
</div>
<!-- Tab Content -->
<div class="min-h-[600px]">
<!-- Overview Tab -->
<div v-if="activeTab === 'overview'" class="space-y-6">
<DashboardOverview />
</div>
<!-- My Orders Tab (Customer) -->
<div v-else-if="activeTab === 'orders'" class="space-y-6">
<OrderHistory />
</div>
<!-- My Store Tab (Merchant) -->
<div v-else-if="activeTab === 'store'" class="space-y-6">
<MerchantStore />
</div>
<!-- Settings Tab -->
<div v-else-if="activeTab === 'settings'" class="space-y-6">
<MarketSettings />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
// import { useAuth } from '@/composables/useAuth'
import { useMarketStore } from '@/stores/market'
import { Badge } from '@/components/ui/badge'
import {
BarChart3,
Package,
Store,
Settings,
} from 'lucide-vue-next'
import DashboardOverview from '@/components/market/DashboardOverview.vue'
import OrderHistory from '@/components/market/OrderHistory.vue'
import MerchantStore from '@/components/market/MerchantStore.vue'
import MarketSettings from '@/components/market/MarketSettings.vue'
// const auth = useAuth()
const marketStore = useMarketStore()
// Local state
const activeTab = ref('overview')
// Computed properties for tab badges
const orderCount = computed(() => Object.keys(marketStore.orders).length)
const pendingOrders = computed(() =>
Object.values(marketStore.orders).filter(o => o.status === 'pending').length
)
// const pendingPayments = computed(() =>
// Object.values(marketStore.orders).filter(o => o.paymentStatus === 'pending').length
// )
// Dashboard tabs
const tabs = computed(() => [
{
id: 'overview',
name: 'Overview',
icon: BarChart3,
badge: null
},
{
id: 'orders',
name: 'My Orders',
icon: Package,
badge: orderCount.value > 0 ? orderCount.value : null
},
{
id: 'store',
name: 'My Store',
icon: Store,
badge: pendingOrders.value > 0 ? pendingOrders.value : null
},
{
id: 'settings',
name: 'Settings',
icon: Settings,
badge: null
}
])
// Lifecycle
onMounted(() => {
console.log('Market Dashboard mounted')
})
</script>

425
src/pages/OrderHistory.vue Normal file
View file

@ -0,0 +1,425 @@
<template>
<div class="container mx-auto px-4 py-8">
<!-- Page Header -->
<div class="mb-8">
<h1 class="text-3xl font-bold text-foreground">Order History</h1>
<p class="text-muted-foreground mt-2">
View and track all your market orders
</p>
<!-- Order Events Status -->
<div class="mt-3 flex items-center gap-2">
<div class="flex items-center gap-2">
<div
class="w-2 h-2 rounded-full"
:class="orderEvents.isSubscribed ? 'bg-green-500' : 'bg-yellow-500'"
></div>
<span class="text-sm text-muted-foreground">
{{ orderEvents.isSubscribed ? 'Listening for order updates' : 'Connecting to order events...' }}
</span>
</div>
<div v-if="orderEvents.lastEventTimestamp.value > 0" class="text-xs text-muted-foreground">
Last update: {{ formatDate(orderEvents.lastEventTimestamp.value) }}
</div>
</div>
</div>
<!-- Filters and Stats -->
<div class="mb-6 flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between">
<!-- Order Stats -->
<div class="flex gap-4 text-sm">
<div class="flex items-center gap-2">
<span class="text-muted-foreground">Total Orders:</span>
<Badge variant="secondary">{{ totalOrders }}</Badge>
</div>
<div class="flex items-center gap-2">
<span class="text-muted-foreground">Pending:</span>
<Badge variant="outline" class="text-yellow-600">{{ pendingOrders }}</Badge>
</div>
<div class="flex items-center gap-2">
<span class="text-muted-foreground">Completed:</span>
<Badge variant="outline" class="text-green-600">{{ completedOrders }}</Badge>
</div>
</div>
<!-- Filter Controls -->
<div class="flex gap-2">
<select v-model="statusFilter" class="w-40 px-3 py-2 border border-input rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring bg-background text-foreground">
<option value="">All Statuses</option>
<option value="pending">Pending</option>
<option value="paid">Paid</option>
<option value="processing">Processing</option>
<option value="shipped">Shipped</option>
<option value="delivered">Delivered</option>
<option value="cancelled">Cancelled</option>
</select>
<select v-model="sortBy" class="w-40 px-3 py-2 border border-input rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring bg-background text-foreground">
<option value="createdAt">Date Created</option>
<option value="total">Order Total</option>
<option value="status">Status</option>
</select>
</div>
</div>
<!-- Orders List -->
<div v-if="filteredOrders.length > 0" class="space-y-4">
<div
v-for="order in sortedOrders"
:key="order.id"
class="bg-card border rounded-lg p-6 hover:shadow-md transition-shadow"
>
<!-- Order Header -->
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 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>
<h3 class="font-semibold text-foreground">Order #{{ order.id.slice(-8) }}</h3>
<p class="text-sm text-muted-foreground">
{{ formatDate(order.createdAt) }}
</p>
<!-- Nostr Status -->
<div v-if="order.sentViaNostr !== undefined" class="flex items-center gap-2 mt-1">
<div v-if="order.sentViaNostr" class="flex items-center gap-1 text-xs text-green-600">
<div class="w-2 h-2 bg-green-500 rounded-full"></div>
Sent via Nostr
</div>
<div v-else class="flex items-center gap-1 text-xs text-red-600">
<div class="w-2 h-2 bg-red-500 rounded-full"></div>
Nostr failed
</div>
</div>
</div>
</div>
<div class="flex items-center gap-3">
<Badge :variant="getStatusVariant(order.status)">
{{ formatStatus(order.status) }}
</Badge>
<!-- Payment Status Indicator -->
<div v-if="order.lightningInvoice" class="flex items-center gap-2">
<Badge
:variant="order.paymentStatus === 'paid' ? 'default' : 'secondary'"
class="text-xs"
>
<div class="flex items-center gap-1">
<div v-if="order.paymentStatus === 'paid'" class="w-2 h-2 bg-green-500 rounded-full"></div>
<div v-else class="w-2 h-2 bg-yellow-500 rounded-full animate-pulse"></div>
{{ order.paymentStatus === 'paid' ? 'Paid' : 'Payment Pending' }}
</div>
</Badge>
</div>
<div class="text-right">
<p class="text-lg font-semibold text-foreground">
{{ formatPrice(order.total, order.currency) }}
</p>
<p class="text-sm text-muted-foreground">{{ order.currency.toUpperCase() }}</p>
</div>
</div>
</div>
<!-- Order Details -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-4">
<!-- Items -->
<div>
<h4 class="font-medium text-foreground mb-2">Items</h4>
<div class="space-y-2">
<div
v-for="item in order.items"
:key="item.productId"
class="flex justify-between text-sm"
>
<span class="text-muted-foreground">
{{ item.productName }} × {{ item.quantity }}
</span>
<span class="font-medium">
{{ formatPrice(item.price * item.quantity, item.currency) }}
</span>
</div>
</div>
</div>
<!-- Order Info -->
<div>
<h4 class="font-medium text-foreground mb-2">Order Details</h4>
<div class="space-y-2 text-sm">
<div class="flex justify-between">
<span class="text-muted-foreground">Subtotal:</span>
<span>{{ formatPrice(order.subtotal, order.currency) }}</span>
</div>
<div class="flex justify-between">
<span class="text-muted-foreground">Shipping:</span>
<span>{{ formatPrice(order.shippingCost, order.currency) }}</span>
</div>
<div class="flex justify-between">
<span class="text-muted-foreground">Payment Method:</span>
<span class="capitalize">{{ order.paymentMethod.replace('_', ' ') }}</span>
</div>
</div>
</div>
</div>
<!-- Contact & Shipping -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-4">
<!-- Contact Info -->
<div v-if="order.contactInfo.email || order.contactInfo.message">
<h4 class="font-medium text-foreground mb-2">Contact Information</h4>
<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>
</div>
</div>
<!-- Shipping Info -->
<div v-if="order.shippingZone">
<h4 class="font-medium text-foreground mb-2">Shipping</h4>
<div class="space-y-1 text-sm text-muted-foreground">
<p>
<span class="font-medium">Zone:</span> {{ order.shippingZone.name }}
</p>
<p v-if="order.shippingZone.estimatedDays">
<span class="font-medium">Est. Delivery:</span> {{ order.shippingZone.estimatedDays }}
</p>
<p v-if="order.contactInfo.address">
<span class="font-medium">Address:</span> {{ order.contactInfo.address }}
</p>
</div>
</div>
</div>
<!-- Nostr Event Details -->
<div v-if="order.sentViaNostr !== undefined" class="mb-4 p-4 bg-muted rounded-lg">
<h4 class="font-medium text-foreground mb-2">Nostr Network Status</h4>
<div class="space-y-2 text-sm">
<div v-if="order.sentViaNostr" class="space-y-1">
<p v-if="order.nostrEventId" class="text-muted-foreground">
<span class="font-medium">Event ID:</span>
<code class="bg-background px-2 py-1 rounded text-xs">{{ order.nostrEventId.slice(0, 16) }}...</code>
</p>
<p v-if="order.nostrEventSig" class="text-muted-foreground">
<span class="font-medium">Signature:</span>
<code class="bg-background px-2 py-1 rounded text-xs">{{ order.nostrEventSig.slice(0, 16) }}...</code>
</p>
<p class="text-green-600">
<span class="font-medium"></span> Order successfully transmitted to merchant via Nostr network
</p>
</div>
<div v-else class="space-y-1">
<p v-if="order.nostrError" class="text-red-600">
<span class="font-medium"></span> Failed to send via Nostr: {{ order.nostrError }}
</p>
<p class="text-yellow-600">
<span class="font-medium"></span> Order stored locally only - merchant may not receive it
</p>
</div>
</div>
</div>
<!-- Payment Status & Actions -->
<div v-if="order.status === 'pending'" class="mb-4 p-4 bg-muted/50 border border-border rounded-lg">
<div class="flex-1">
<h4 class="font-medium text-foreground mb-2">Payment Required</h4>
<div v-if="order.lightningInvoice" class="space-y-2">
<p class="text-sm text-muted-foreground">
<span class="font-medium text-green-600"></span> Lightning invoice received from merchant
</p>
<p class="text-sm text-muted-foreground">
Amount: <span class="font-medium text-foreground">{{ formatPrice(order.total, order.currency) }}</span>
</p>
<p class="text-sm text-muted-foreground">
Status: <span class="font-medium text-foreground">{{ order.paymentStatus === 'paid' ? 'Paid' : 'Pending Payment' }}</span>
</p>
</div>
<div v-else class="space-y-2">
<p class="text-sm text-muted-foreground">
<span class="font-medium text-amber-600"></span> Waiting for merchant to generate payment invoice
</p>
<p class="text-sm text-muted-foreground">
The merchant will send you a Lightning invoice via Nostr once they process your order
</p>
</div>
</div>
</div>
<!-- Actions -->
<div class="flex justify-end gap-2 pt-4 border-t border-border">
<Button
v-if="order.status === 'pending'"
variant="outline"
size="sm"
@click="cancelOrder(order.id)"
>
Cancel Order
</Button>
<Button
v-if="order.lightningInvoice"
variant="default"
size="sm"
@click="togglePaymentDisplay(order.id)"
>
<Wallet class="w-4 h-4 mr-2" />
{{ expandedPayments.has(order.id) ? 'Hide' : 'Show' }} Payment
</Button>
<Button
variant="outline"
size="sm"
@click="copyOrderId(order.id)"
>
Copy Order ID
</Button>
</div>
<!-- Payment Display (Expandable) -->
<div v-if="expandedPayments.has(order.id) && order.lightningInvoice" class="mt-4 pt-4 border-t border-border">
<PaymentDisplay
:order-id="order.id"
/>
</div>
</div>
</div>
<!-- Empty State -->
<div v-else class="text-center py-12">
<div class="w-16 h-16 bg-muted rounded-full flex items-center justify-center mx-auto mb-4">
<Package class="w-8 h-8 text-muted-foreground/50" />
</div>
<h3 class="text-lg font-medium text-foreground mb-2">No orders yet</h3>
<p class="text-muted-foreground mb-6">
Start shopping in the market to see your order history here
</p>
<Button @click="router.push('/market')" variant="default">
Browse Market
</Button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useMarketStore } from '@/stores/market'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Package, Wallet } from 'lucide-vue-next'
import type { OrderStatus } from '@/stores/market'
import PaymentDisplay from '@/components/market/PaymentDisplay.vue'
import { orderEvents } from '@/composables/useOrderEvents'
const router = useRouter()
const marketStore = useMarketStore()
// Local state
const statusFilter = ref('')
const sortBy = ref('createdAt')
const expandedPayments = ref(new Set<string>())
// Computed properties
const allOrders = computed(() => Object.values(marketStore.orders))
const filteredOrders = computed(() => {
if (!statusFilter.value) return allOrders.value
return allOrders.value.filter(order => order.status === statusFilter.value)
})
const sortedOrders = computed(() => {
const orders = [...filteredOrders.value]
switch (sortBy.value) {
case 'total':
return orders.sort((a, b) => b.total - a.total)
case 'status':
return orders.sort((a, b) => a.status.localeCompare(b.status))
case 'createdAt':
default:
return orders.sort((a, b) => b.createdAt - a.createdAt)
}
})
const totalOrders = computed(() => allOrders.value.length)
const pendingOrders = computed(() => allOrders.value.filter(o => o.status === 'pending').length)
const completedOrders = computed(() =>
allOrders.value.filter(o => ['delivered', 'shipped'].includes(o.status)).length
)
// 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 cancelOrder = (orderId: string) => {
// TODO: Implement order cancellation
console.log('Cancelling order:', orderId)
}
// const viewPayment = (_order: any) => {
// // TODO: Implement payment viewing
// console.log('Viewing payment for order:', _order.id)
// }
const copyOrderId = async (orderId: string) => {
try {
await navigator.clipboard.writeText(orderId)
// TODO: Show toast notification
console.log('Order ID copied to clipboard')
} catch (err) {
console.error('Failed to copy order ID:', err)
}
}
const togglePaymentDisplay = (orderId: string) => {
if (expandedPayments.value.has(orderId)) {
expandedPayments.value.delete(orderId)
} else {
expandedPayments.value.add(orderId)
}
}
// Load orders on mount
onMounted(() => {
// Orders are already loaded in the market store
console.log('Order History page loaded with', allOrders.value.length, 'orders')
// Start listening for order events if not already listening
if (!orderEvents.isSubscribed.value) {
orderEvents.startListening()
}
})
</script>

View file

@ -184,8 +184,8 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import RelayHubStatus from '@/components/RelayHubStatus.vue'
import { useRelayHub } from '@/composables/useRelayHub'
import { config } from '@/lib/config'
import { relayHubComposable } from '@/composables/useRelayHub'
const {
isConnected,
@ -196,7 +196,7 @@ const {
initialize,
connect,
subscribe
} = useRelayHub()
} = relayHubComposable
// Test state
const isTesting = ref(false)