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 { // Import StallApiResponse from types/market.ts
id: string import type { StallApiResponse } from '../types/market'
wallet: string
name: string // Use StallApiResponse as the API response type
currency: string export type Stall = StallApiResponse
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 interface CreateMerchantRequest { export interface CreateMerchantRequest {
config: { config: {

View file

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

View file

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