feat: consolidate Stall types and create reusable CartButton component

## Type Consolidation
- Add StallApiResponse interface matching LNbits backend structure
- Update domain Stall interface with cleaner, app-friendly properties
- Create mapApiResponseToStall() mapper function for API-to-domain conversion
- Remove duplicate Stall type definition from nostrmarketAPI.ts
- Update CheckoutPage to use standardized shipping property
- Verify types against LNbits reference implementation

## UI Components
- Create reusable CartButton.vue component with proper separation of concerns
- Remove duplicate cart button code from MarketPage and StallView
- Add consistent cart functionality across all market pages
- Fix missing cart button in StallView
- Improve code maintainability with DRY principles

## Bug Fixes
- Fix ProductDetailDialog add-to-cart functionality by using correct cart system
- Resolve cart system mismatch between legacy addToCart and stall-based totalCartItems
- Update ProductCard to emit events properly instead of direct store call
- Ensure consistent event flow: ProductCard → ProductGrid → MarketPage → Store

## Technical Improvements
- Follow established Product type consolidation pattern for consistency
- Maintain type safety between API responses and domain models
- Enable easier API evolution without breaking domain logic
- Optimize bundle splitting with component extraction
This commit is contained in:
padreug 2025-09-27 01:31:52 +02:00
parent da5c4d6de1
commit 8821f604be
3 changed files with 38 additions and 39 deletions

View file

@ -17,25 +17,11 @@ export interface Merchant {
}
}
export interface Stall {
id: string
wallet: string
name: string
currency: string
shipping_zones: Array<{
id: string
name: string
cost: number
countries: string[]
}>
config: {
image_url?: string
description?: string
}
pending: boolean
event_id?: string
event_created_at?: number
}
// Import StallApiResponse from types/market.ts
import type { StallApiResponse } from '../types/market'
// Use StallApiResponse as the API response type
export type Stall = StallApiResponse
export interface CreateMerchantRequest {
config: {

View file

@ -13,6 +13,7 @@ export interface Market {
}
}
// Domain Model - Single source of truth for Stall
export interface Stall {
id: string
pubkey: string
@ -20,8 +21,7 @@ export interface Stall {
description?: string
logo?: string
categories?: string[]
shipping?: ShippingZone[]
shipping_zones?: ShippingZone[] // LNbits format
shipping: ShippingZone[]
currency: string
nostrEventId?: string
}
@ -48,7 +48,7 @@ export interface Product {
}
// Type aliases for API responses - imported from services
export type { ProductApiResponse, Stall as StallApiResponse } from '../services/nostrmarketAPI'
export type { ProductApiResponse } from '../services/nostrmarketAPI'
// Mapping function to convert API response to domain model
export function mapApiResponseToProduct(
@ -76,17 +76,19 @@ export function mapApiResponseToProduct(
}
}
// Mapping function to convert API response to domain model for Stall
// Mapper function to convert API response to domain model
export function mapApiResponseToStall(
apiStall: import('../services/nostrmarketAPI').Stall
apiStall: StallApiResponse,
pubkey: string = '',
categories: string[] = []
): Stall {
return {
id: apiStall.id,
pubkey: '', // API Stall doesn't have pubkey, need to set from merchant context
pubkey,
name: apiStall.name,
description: apiStall.config?.description,
logo: apiStall.config?.image_url,
categories: [], // API Stall doesn't have categories
categories,
shipping: apiStall.shipping_zones?.map(zone => ({
id: zone.id,
name: zone.name,
@ -96,17 +98,7 @@ export function mapApiResponseToStall(
description: `${zone.name} shipping`,
estimatedDays: undefined,
requiresPhysicalShipping: true
})),
shipping_zones: apiStall.shipping_zones?.map(zone => ({
id: zone.id,
name: zone.name,
cost: zone.cost,
currency: apiStall.currency,
countries: zone.countries,
description: `${zone.name} shipping`,
estimatedDays: undefined,
requiresPhysicalShipping: true
})),
})) || [],
currency: apiStall.currency,
nostrEventId: apiStall.event_id
}
@ -173,6 +165,27 @@ export interface ShippingZone {
requiresPhysicalShipping?: boolean
}
// API Response Types - Raw data from LNbits backend
export interface StallApiResponse {
id: string
wallet: string
name: string
currency: string
shipping_zones: Array<{
id: string
name: string
cost: number
countries: string[]
}>
config: {
image_url?: string
description?: string
}
pending: boolean
event_id?: string
event_created_at?: number
}
export type OrderStatus = 'pending' | 'paid' | 'shipped' | 'delivered' | 'cancelled' | 'processing'
export type PaymentMethod = 'lightning' | 'btc_onchain'

View file

@ -349,8 +349,8 @@ const orderTotal = computed(() => {
const availableShippingZones = computed(() => {
if (!currentStall.value) return []
// Check if stall has shipping_zones (LNbits format) or shipping (nostr-market-app format)
const zones = currentStall.value.shipping_zones || currentStall.value.shipping || []
// Use standardized shipping property from domain model
const zones = currentStall.value.shipping || []
// Ensure zones have required properties and determine shipping requirements
return zones.map(zone => {