import { defineStore } from 'pinia' import { ref, computed } from 'vue' import { config } from '@/lib/config' // Types export interface Market { d: string pubkey: string relays: string[] selected: boolean opts: { name: string description?: string logo?: string banner?: string merchants: string[] ui: Record } } export interface Stall { id: string pubkey: string name: string description?: string logo?: string categories?: string[] shipping?: Record } export interface Product { id: string stall_id: string stallName: string name: string description?: string price: number currency: string quantity: number images?: string[] categories?: string[] createdAt: number updatedAt: number } export interface Order { id: string stall_id: string product_id: string buyer_pubkey: string seller_pubkey: string quantity: number total_price: number currency: string status: 'pending' | 'paid' | 'shipped' | 'delivered' | 'cancelled' payment_request?: string created_at: number updated_at: number } export interface FilterData { categories: string[] merchants: string[] stalls: string[] currency: string | null priceFrom: number | null priceTo: number | null } export interface SortOptions { field: string order: 'asc' | 'desc' } export const useMarketStore = defineStore('market', () => { // Core market state const markets = ref([]) const stalls = ref([]) const products = ref([]) const orders = ref>({}) const profiles = ref>({}) // Active selections const activeMarket = ref(null) const activeStall = ref(null) const activeProduct = ref(null) // UI state const isLoading = ref(false) const searchText = ref('') const showFilterDetails = ref(false) // Filtering and sorting const filterData = ref({ categories: [], merchants: [], stalls: [], currency: null, priceFrom: null, priceTo: null }) const sortOptions = ref({ field: 'name', order: 'asc' }) // Shopping cart const shoppingCart = ref>({}) // Computed properties const filteredProducts = computed(() => { let filtered = products.value // Search filter if (searchText.value) { const searchLower = searchText.value.toLowerCase() filtered = filtered.filter(product => product.name.toLowerCase().includes(searchLower) || product.description?.toLowerCase().includes(searchLower) || product.stallName.toLowerCase().includes(searchLower) ) } // Category filter if (filterData.value.categories.length > 0) { filtered = filtered.filter(product => product.categories?.some(cat => filterData.value.categories.includes(cat)) ) } // Merchant filter if (filterData.value.merchants.length > 0) { filtered = filtered.filter(product => filterData.value.merchants.includes(product.stall_id) ) } // Stall filter if (filterData.value.stalls.length > 0) { filtered = filtered.filter(product => filterData.value.stalls.includes(product.stall_id) ) } // Currency filter if (filterData.value.currency) { filtered = filtered.filter(product => product.currency === filterData.value.currency ) } // Price range filter if (filterData.value.priceFrom !== null) { filtered = filtered.filter(product => product.price >= filterData.value.priceFrom! ) } if (filterData.value.priceTo !== null) { filtered = filtered.filter(product => product.price <= filterData.value.priceTo! ) } // Sort filtered.sort((a, b) => { const aVal = a[sortOptions.value.field as keyof Product] const bVal = b[sortOptions.value.field as keyof Product] if (typeof aVal === 'string' && typeof bVal === 'string') { return sortOptions.value.order === 'asc' ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal) } if (typeof aVal === 'number' && typeof bVal === 'number') { return sortOptions.value.order === 'asc' ? aVal - bVal : bVal - aVal } return 0 }) return filtered }) const allCategories = computed(() => { const categories = new Set() products.value.forEach(product => { product.categories?.forEach(cat => categories.add(cat)) }) return Array.from(categories).map(category => ({ category, count: products.value.filter(p => p.categories?.includes(category)).length, selected: filterData.value.categories.includes(category) })) }) const cartTotal = computed(() => { return Object.values(shoppingCart.value).reduce((total, item) => { return total + (item.product.price * item.quantity) }, 0) }) const cartItemCount = computed(() => { return Object.values(shoppingCart.value).reduce((count, item) => { return count + item.quantity }, 0) }) // Actions const setLoading = (loading: boolean) => { isLoading.value = loading } const setSearchText = (text: string) => { searchText.value = text } const setActiveMarket = (market: Market | null) => { activeMarket.value = market } const setActiveStall = (stall: Stall | null) => { activeStall.value = stall } const setActiveProduct = (product: Product | null) => { activeProduct.value = product } const addProduct = (product: Product) => { const existingIndex = products.value.findIndex(p => p.id === product.id) if (existingIndex >= 0) { products.value[existingIndex] = product } else { products.value.push(product) } } const addStall = (stall: Stall) => { const existingIndex = stalls.value.findIndex(s => s.id === stall.id) if (existingIndex >= 0) { stalls.value[existingIndex] = stall } else { stalls.value.push(stall) } } const addMarket = (market: Market) => { const existingIndex = markets.value.findIndex(m => m.d === market.d) if (existingIndex >= 0) { markets.value[existingIndex] = market } else { markets.value.push(market) } } const addToCart = (product: Product, quantity: number = 1) => { const existing = shoppingCart.value[product.id] if (existing) { existing.quantity += quantity } else { shoppingCart.value[product.id] = { product, quantity } } } const removeFromCart = (productId: string) => { delete shoppingCart.value[productId] } const updateCartQuantity = (productId: string, quantity: number) => { if (quantity <= 0) { removeFromCart(productId) } else { const item = shoppingCart.value[productId] if (item) { item.quantity = quantity } } } const clearCart = () => { shoppingCart.value = {} } const updateFilterData = (newFilterData: Partial) => { filterData.value = { ...filterData.value, ...newFilterData } } const clearFilters = () => { filterData.value = { categories: [], merchants: [], stalls: [], currency: null, priceFrom: null, priceTo: null } } const toggleCategoryFilter = (category: string) => { const index = filterData.value.categories.indexOf(category) if (index >= 0) { filterData.value.categories.splice(index, 1) } else { filterData.value.categories.push(category) } } const updateSortOptions = (field: string, order: 'asc' | 'desc' = 'asc') => { sortOptions.value = { field, order } } const formatPrice = (price: number, currency: string) => { if (currency === 'sat') { return `${price} sats` } return new Intl.NumberFormat('en-US', { style: 'currency', currency: currency.toUpperCase() }).format(price) } return { // State markets: readonly(markets), stalls: readonly(stalls), products: readonly(products), orders: readonly(orders), profiles: readonly(profiles), activeMarket: readonly(activeMarket), activeStall: readonly(activeStall), activeProduct: readonly(activeProduct), isLoading: readonly(isLoading), searchText: readonly(searchText), showFilterDetails: readonly(showFilterDetails), filterData: readonly(filterData), sortOptions: readonly(sortOptions), shoppingCart: readonly(shoppingCart), // Computed filteredProducts, allCategories, cartTotal, cartItemCount, // Actions setLoading, setSearchText, setActiveMarket, setActiveStall, setActiveProduct, addProduct, addStall, addMarket, addToCart, removeFromCart, updateCartQuantity, clearCart, updateFilterData, clearFilters, toggleCategoryFilter, updateSortOptions, formatPrice } })