Refactor authentication and async operation handling in useAuth composable
- Introduce useMultiAsyncOperation to manage multiple async operations in useAuth, enhancing error handling and loading state management. - Replace manual loading and error state management with standardized async operation patterns for initialize, login, register, and logout functions. - Update related components to utilize the new async operation structure, improving code clarity and maintainability. - Add useAsyncOperation to other composables (useChat, useTicketPurchase, useMarket) for consistent async handling across the application.
This commit is contained in:
parent
e0443742c5
commit
7c439361b7
5 changed files with 298 additions and 133 deletions
|
|
@ -1,31 +1,45 @@
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { lnbitsAPI, type User, type LoginCredentials, type RegisterData } from '@/lib/api/lnbits'
|
import { lnbitsAPI, type User, type LoginCredentials, type RegisterData } from '@/lib/api/lnbits'
|
||||||
|
import { useMultiAsyncOperation } from '@/core/composables/useAsyncOperation'
|
||||||
|
|
||||||
const currentUser = ref<User | null>(null)
|
const currentUser = ref<User | null>(null)
|
||||||
const isLoading = ref(false)
|
|
||||||
const error = ref<string | null>(null)
|
// Shared async operations for auth actions
|
||||||
|
const authOperations = useMultiAsyncOperation<{
|
||||||
|
initialize: User | null
|
||||||
|
login: User
|
||||||
|
register: User
|
||||||
|
logout: void
|
||||||
|
}>()
|
||||||
|
|
||||||
export function useAuth() {
|
export function useAuth() {
|
||||||
const isAuthenticated = computed(() => !!currentUser.value)
|
const isAuthenticated = computed(() => !!currentUser.value)
|
||||||
|
|
||||||
|
// Get operation instances
|
||||||
|
const initializeOp = authOperations.createOperation('initialize')
|
||||||
|
const loginOp = authOperations.createOperation('login')
|
||||||
|
const registerOp = authOperations.createOperation('register')
|
||||||
|
const logoutOp = authOperations.createOperation('logout')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize authentication on app start
|
* Initialize authentication on app start
|
||||||
*/
|
*/
|
||||||
async function initialize(): Promise<void> {
|
async function initialize(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
isLoading.value = true
|
await initializeOp.execute(async () => {
|
||||||
error.value = null
|
if (lnbitsAPI.isAuthenticated()) {
|
||||||
|
const user = await lnbitsAPI.getCurrentUser()
|
||||||
if (lnbitsAPI.isAuthenticated()) {
|
currentUser.value = user
|
||||||
const user = await lnbitsAPI.getCurrentUser()
|
return user
|
||||||
currentUser.value = user
|
}
|
||||||
}
|
return null
|
||||||
} catch (err) {
|
}, {
|
||||||
error.value = err instanceof Error ? err.message : 'Failed to initialize authentication'
|
errorMessage: 'Failed to initialize authentication',
|
||||||
// Clear invalid token
|
showToast: false // Don't show toast for initialization errors
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
// Clear invalid token on error
|
||||||
await logout()
|
await logout()
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,88 +47,77 @@ export function useAuth() {
|
||||||
* Login with username and password
|
* Login with username and password
|
||||||
*/
|
*/
|
||||||
async function login(credentials: LoginCredentials): Promise<void> {
|
async function login(credentials: LoginCredentials): Promise<void> {
|
||||||
try {
|
await loginOp.execute(async () => {
|
||||||
isLoading.value = true
|
|
||||||
error.value = null
|
|
||||||
|
|
||||||
await lnbitsAPI.login(credentials)
|
await lnbitsAPI.login(credentials)
|
||||||
|
|
||||||
// Get user details
|
// Get user details
|
||||||
const user = await lnbitsAPI.getCurrentUser()
|
const user = await lnbitsAPI.getCurrentUser()
|
||||||
currentUser.value = user
|
currentUser.value = user
|
||||||
} catch (err) {
|
return user
|
||||||
error.value = err instanceof Error ? err.message : 'Login failed'
|
}, {
|
||||||
throw err
|
errorMessage: 'Login failed'
|
||||||
} finally {
|
})
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register new user
|
* Register new user
|
||||||
*/
|
*/
|
||||||
async function register(data: RegisterData): Promise<void> {
|
async function register(data: RegisterData): Promise<void> {
|
||||||
try {
|
await registerOp.execute(async () => {
|
||||||
isLoading.value = true
|
|
||||||
error.value = null
|
|
||||||
|
|
||||||
await lnbitsAPI.register(data)
|
await lnbitsAPI.register(data)
|
||||||
|
|
||||||
// Get user details
|
// Get user details
|
||||||
const user = await lnbitsAPI.getCurrentUser()
|
const user = await lnbitsAPI.getCurrentUser()
|
||||||
currentUser.value = user
|
currentUser.value = user
|
||||||
} catch (err) {
|
return user
|
||||||
error.value = err instanceof Error ? err.message : 'Registration failed'
|
}, {
|
||||||
throw err
|
errorMessage: 'Registration failed'
|
||||||
} finally {
|
})
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logout and clear user data
|
* Logout and clear user data
|
||||||
*/
|
*/
|
||||||
async function logout(): Promise<void> {
|
async function logout(): Promise<void> {
|
||||||
// Clear local state
|
await logoutOp.execute(async () => {
|
||||||
lnbitsAPI.logout()
|
// Clear local state
|
||||||
currentUser.value = null
|
lnbitsAPI.logout()
|
||||||
error.value = null
|
currentUser.value = null
|
||||||
|
// Clear all auth operation states
|
||||||
|
authOperations.clearAll()
|
||||||
|
}, {
|
||||||
|
showToast: false // Don't show toast for logout
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update user password
|
* Update user password
|
||||||
*/
|
*/
|
||||||
async function updatePassword(currentPassword: string, newPassword: string): Promise<void> {
|
async function updatePassword(currentPassword: string, newPassword: string): Promise<void> {
|
||||||
try {
|
const updatePasswordOp = authOperations.createOperation('updatePassword' as any)
|
||||||
isLoading.value = true
|
|
||||||
error.value = null
|
return await updatePasswordOp.execute(async () => {
|
||||||
|
|
||||||
const updatedUser = await lnbitsAPI.updatePassword(currentPassword, newPassword)
|
const updatedUser = await lnbitsAPI.updatePassword(currentPassword, newPassword)
|
||||||
currentUser.value = updatedUser
|
currentUser.value = updatedUser
|
||||||
} catch (err) {
|
return updatedUser
|
||||||
error.value = err instanceof Error ? err.message : 'Failed to update password'
|
}, {
|
||||||
throw err
|
errorMessage: 'Failed to update password'
|
||||||
} finally {
|
})
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update user profile
|
* Update user profile
|
||||||
*/
|
*/
|
||||||
async function updateProfile(data: Partial<User>): Promise<void> {
|
async function updateProfile(data: Partial<User>): Promise<void> {
|
||||||
try {
|
const updateProfileOp = authOperations.createOperation('updateProfile' as any)
|
||||||
isLoading.value = true
|
|
||||||
error.value = null
|
return await updateProfileOp.execute(async () => {
|
||||||
|
|
||||||
const updatedUser = await lnbitsAPI.updateProfile(data)
|
const updatedUser = await lnbitsAPI.updateProfile(data)
|
||||||
currentUser.value = updatedUser
|
currentUser.value = updatedUser
|
||||||
} catch (err) {
|
return updatedUser
|
||||||
error.value = err instanceof Error ? err.message : 'Failed to update profile'
|
}, {
|
||||||
throw err
|
errorMessage: 'Failed to update profile'
|
||||||
} finally {
|
})
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -145,8 +148,9 @@ export function useAuth() {
|
||||||
// State
|
// State
|
||||||
currentUser: computed(() => currentUser.value),
|
currentUser: computed(() => currentUser.value),
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
isLoading,
|
isLoading: computed(() => authOperations.isAnyLoading()),
|
||||||
error,
|
error: computed(() => authOperations.hasAnyError() ?
|
||||||
|
(initializeOp.error.value || loginOp.error.value || registerOp.error.value || logoutOp.error.value) : null),
|
||||||
userDisplay,
|
userDisplay,
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
|
|
||||||
164
src/core/composables/useAsyncOperation.ts
Normal file
164
src/core/composables/useAsyncOperation.ts
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
import { ref, type Ref } from 'vue'
|
||||||
|
import { toast } from 'vue-sonner'
|
||||||
|
|
||||||
|
export interface AsyncOperationOptions {
|
||||||
|
successMessage?: string
|
||||||
|
errorMessage?: string
|
||||||
|
showToast?: boolean
|
||||||
|
showSuccessToast?: boolean
|
||||||
|
showErrorToast?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AsyncOperationState<T> {
|
||||||
|
isLoading: Ref<boolean>
|
||||||
|
error: Ref<string | null>
|
||||||
|
data: Ref<T | null>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AsyncOperationReturn<T> extends AsyncOperationState<T> {
|
||||||
|
execute: (operation: () => Promise<T>, options?: AsyncOperationOptions) => Promise<T | null>
|
||||||
|
reset: () => void
|
||||||
|
clear: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable for standardized async operation handling
|
||||||
|
* Eliminates duplicate loading/error/success patterns across modules
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const { isLoading, error, data, execute } = useAsyncOperation<OrderData>()
|
||||||
|
*
|
||||||
|
* const handleOrder = async () => {
|
||||||
|
* await execute(async () => {
|
||||||
|
* return await createOrder(orderData)
|
||||||
|
* }, {
|
||||||
|
* successMessage: 'Order created successfully!',
|
||||||
|
* errorMessage: 'Failed to create order'
|
||||||
|
* })
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useAsyncOperation<T = any>(): AsyncOperationReturn<T> {
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
const data = ref(null) as Ref<T | null>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute an async operation with standardized error handling and loading states
|
||||||
|
*/
|
||||||
|
const execute = async (
|
||||||
|
operation: () => Promise<T>,
|
||||||
|
options: AsyncOperationOptions = {}
|
||||||
|
): Promise<T | null> => {
|
||||||
|
const {
|
||||||
|
successMessage,
|
||||||
|
errorMessage = 'Operation failed',
|
||||||
|
showToast = true,
|
||||||
|
showSuccessToast = showToast,
|
||||||
|
showErrorToast = showToast
|
||||||
|
} = options
|
||||||
|
|
||||||
|
try {
|
||||||
|
isLoading.value = true
|
||||||
|
error.value = null
|
||||||
|
|
||||||
|
const result = await operation()
|
||||||
|
data.value = result
|
||||||
|
|
||||||
|
// Show success toast if configured
|
||||||
|
if (showSuccessToast && successMessage) {
|
||||||
|
toast.success(successMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
const errorMsg = err instanceof Error ? err.message : String(err)
|
||||||
|
error.value = errorMsg
|
||||||
|
|
||||||
|
// Show error toast if configured
|
||||||
|
if (showErrorToast) {
|
||||||
|
toast.error(errorMessage, {
|
||||||
|
description: errorMsg !== errorMessage ? errorMsg : undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-throw to allow caller to handle if needed
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the operation state (clear error, keep data)
|
||||||
|
*/
|
||||||
|
const reset = (): void => {
|
||||||
|
isLoading.value = false
|
||||||
|
error.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all state (error, data, loading)
|
||||||
|
*/
|
||||||
|
const clear = (): void => {
|
||||||
|
isLoading.value = false
|
||||||
|
error.value = null
|
||||||
|
data.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// State
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
data,
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
execute,
|
||||||
|
reset,
|
||||||
|
clear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialized version for operations that don't return data
|
||||||
|
*/
|
||||||
|
export function useAsyncAction(): Omit<AsyncOperationReturn<void>, 'data'> {
|
||||||
|
const { data, ...rest } = useAsyncOperation<void>()
|
||||||
|
return rest
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiple async operations manager
|
||||||
|
* Useful when you need to track multiple independent operations
|
||||||
|
*/
|
||||||
|
export function useMultiAsyncOperation<T extends Record<string, any>>() {
|
||||||
|
const operations = ref<Record<keyof T, AsyncOperationReturn<any>>>({} as any)
|
||||||
|
|
||||||
|
const createOperation = <K extends keyof T>(key: K): AsyncOperationReturn<T[K]> => {
|
||||||
|
if (!operations.value[key]) {
|
||||||
|
operations.value[key] = useAsyncOperation<T[K]>()
|
||||||
|
}
|
||||||
|
return operations.value[key] as AsyncOperationReturn<T[K]>
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAnyLoading = (): boolean => {
|
||||||
|
return Object.values(operations.value).some((op: any) => op.isLoading.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasAnyError = (): boolean => {
|
||||||
|
return Object.values(operations.value).some((op: any) => op.error.value !== null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearAll = (): void => {
|
||||||
|
Object.values(operations.value).forEach((op: any) => op.clear())
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
operations,
|
||||||
|
createOperation,
|
||||||
|
isAnyLoading,
|
||||||
|
hasAnyError,
|
||||||
|
clearAll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ import { ref, computed } from 'vue'
|
||||||
import { injectService } from '@/core/di-container'
|
import { injectService } from '@/core/di-container'
|
||||||
import type { ChatService } from '../services/chat-service'
|
import type { ChatService } from '../services/chat-service'
|
||||||
import type { ChatPeer } from '../types'
|
import type { ChatPeer } from '../types'
|
||||||
|
import { useMultiAsyncOperation } from '@/core/composables/useAsyncOperation'
|
||||||
|
|
||||||
// Service token for chat service
|
// Service token for chat service
|
||||||
export const CHAT_SERVICE_TOKEN = Symbol('chatService')
|
export const CHAT_SERVICE_TOKEN = Symbol('chatService')
|
||||||
|
|
@ -14,8 +15,15 @@ export function useChat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedPeer = ref<string | null>(null)
|
const selectedPeer = ref<string | null>(null)
|
||||||
const isLoading = ref(false)
|
|
||||||
const error = ref<string | null>(null)
|
// Async operations
|
||||||
|
const asyncOps = useMultiAsyncOperation<{
|
||||||
|
sendMessage: void
|
||||||
|
refreshPeers: void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const sendMessageOp = asyncOps.createOperation('sendMessage')
|
||||||
|
const refreshPeersOp = asyncOps.createOperation('refreshPeers')
|
||||||
|
|
||||||
// Computed properties
|
// Computed properties
|
||||||
const peers = computed(() => chatService.allPeers.value)
|
const peers = computed(() => chatService.allPeers.value)
|
||||||
|
|
@ -41,17 +49,11 @@ export function useChat() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading.value = true
|
return await sendMessageOp.execute(async () => {
|
||||||
error.value = null
|
await chatService.sendMessage(selectedPeer.value!, content.trim())
|
||||||
|
}, {
|
||||||
try {
|
errorMessage: 'Failed to send message'
|
||||||
await chatService.sendMessage(selectedPeer.value, content.trim())
|
})
|
||||||
} catch (err) {
|
|
||||||
error.value = err instanceof Error ? err.message : 'Failed to send message'
|
|
||||||
console.error('Send message error:', err)
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const addPeer = (pubkey: string, name?: string): ChatPeer => {
|
const addPeer = (pubkey: string, name?: string): ChatPeer => {
|
||||||
|
|
@ -63,23 +65,22 @@ export function useChat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshPeers = async () => {
|
const refreshPeers = async () => {
|
||||||
isLoading.value = true
|
return await refreshPeersOp.execute(async () => {
|
||||||
error.value = null
|
|
||||||
try {
|
|
||||||
await chatService.refreshPeers()
|
await chatService.refreshPeers()
|
||||||
} catch (err) {
|
}, {
|
||||||
error.value = err instanceof Error ? err.message : 'Failed to refresh peers'
|
errorMessage: 'Failed to refresh peers'
|
||||||
console.error('Failed to refresh peers:', err)
|
})
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// State
|
// State
|
||||||
selectedPeer,
|
selectedPeer,
|
||||||
isLoading,
|
isSendingMessage: sendMessageOp.isLoading,
|
||||||
error,
|
sendMessageError: sendMessageOp.error,
|
||||||
|
isRefreshingPeers: refreshPeersOp.isLoading,
|
||||||
|
refreshPeersError: refreshPeersOp.error,
|
||||||
|
isLoading: computed(() => asyncOps.isAnyLoading()),
|
||||||
|
error: computed(() => sendMessageOp.error.value || refreshPeersOp.error.value),
|
||||||
|
|
||||||
// Computed
|
// Computed
|
||||||
peers,
|
peers,
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,15 @@ import { ref, computed, onUnmounted } from 'vue'
|
||||||
import { purchaseTicket, checkPaymentStatus, payInvoiceWithWallet } from '@/lib/api/events'
|
import { purchaseTicket, checkPaymentStatus, payInvoiceWithWallet } from '@/lib/api/events'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
import { toast } from 'vue-sonner'
|
import { toast } from 'vue-sonner'
|
||||||
|
import { useAsyncOperation } from '@/core/composables/useAsyncOperation'
|
||||||
|
|
||||||
export function useTicketPurchase() {
|
export function useTicketPurchase() {
|
||||||
const { isAuthenticated, currentUser } = useAuth()
|
const { isAuthenticated, currentUser } = useAuth()
|
||||||
|
|
||||||
|
// Async operations
|
||||||
|
const purchaseOperation = useAsyncOperation()
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const isLoading = ref(false)
|
|
||||||
const error = ref<string | null>(null)
|
|
||||||
const paymentHash = ref<string | null>(null)
|
const paymentHash = ref<string | null>(null)
|
||||||
const paymentRequest = ref<string | null>(null)
|
const paymentRequest = ref<string | null>(null)
|
||||||
const qrCode = ref<string | null>(null)
|
const qrCode = ref<string | null>(null)
|
||||||
|
|
@ -50,7 +52,7 @@ export function useTicketPurchase() {
|
||||||
qrCode.value = dataUrl
|
qrCode.value = dataUrl
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error generating QR code:', err)
|
console.error('Error generating QR code:', err)
|
||||||
error.value = 'Failed to generate QR code'
|
// Note: error handling is now managed by the purchaseOperation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,16 +100,15 @@ export function useTicketPurchase() {
|
||||||
throw new Error('User must be authenticated to purchase tickets')
|
throw new Error('User must be authenticated to purchase tickets')
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading.value = true
|
return await purchaseOperation.execute(async () => {
|
||||||
error.value = null
|
// Clear previous state
|
||||||
paymentHash.value = null
|
paymentHash.value = null
|
||||||
paymentRequest.value = null
|
paymentRequest.value = null
|
||||||
qrCode.value = null
|
qrCode.value = null
|
||||||
ticketQRCode.value = null
|
ticketQRCode.value = null
|
||||||
purchasedTicketId.value = null
|
purchasedTicketId.value = null
|
||||||
showTicketQR.value = false
|
showTicketQR.value = false
|
||||||
|
|
||||||
try {
|
|
||||||
// Get the invoice
|
// Get the invoice
|
||||||
const invoice = await purchaseTicket(eventId)
|
const invoice = await purchaseTicket(eventId)
|
||||||
paymentHash.value = invoice.payment_hash
|
paymentHash.value = invoice.payment_hash
|
||||||
|
|
@ -133,12 +134,11 @@ export function useTicketPurchase() {
|
||||||
// No wallet balance, proceed with manual payment
|
// No wallet balance, proceed with manual payment
|
||||||
await startPaymentStatusCheck(eventId, invoice.payment_hash)
|
await startPaymentStatusCheck(eventId, invoice.payment_hash)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
error.value = err instanceof Error ? err.message : 'Failed to purchase ticket'
|
return invoice
|
||||||
console.error('Error purchasing ticket:', err)
|
}, {
|
||||||
} finally {
|
errorMessage: 'Failed to purchase ticket'
|
||||||
isLoading.value = false
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start payment status check
|
// Start payment status check
|
||||||
|
|
@ -184,8 +184,7 @@ export function useTicketPurchase() {
|
||||||
|
|
||||||
// Reset payment state
|
// Reset payment state
|
||||||
function resetPaymentState() {
|
function resetPaymentState() {
|
||||||
isLoading.value = false
|
purchaseOperation.clear()
|
||||||
error.value = null
|
|
||||||
paymentHash.value = null
|
paymentHash.value = null
|
||||||
paymentRequest.value = null
|
paymentRequest.value = null
|
||||||
qrCode.value = null
|
qrCode.value = null
|
||||||
|
|
@ -215,8 +214,8 @@ export function useTicketPurchase() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// State
|
// State
|
||||||
isLoading,
|
isLoading: purchaseOperation.isLoading,
|
||||||
error,
|
error: purchaseOperation.error,
|
||||||
paymentHash,
|
paymentHash,
|
||||||
paymentRequest,
|
paymentRequest,
|
||||||
qrCode,
|
qrCode,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
||||||
import { config } from '@/lib/config'
|
import { config } from '@/lib/config'
|
||||||
import { nostrmarketService } from '../services/nostrmarketService'
|
import { nostrmarketService } from '../services/nostrmarketService'
|
||||||
import { nip04 } from 'nostr-tools'
|
import { nip04 } from 'nostr-tools'
|
||||||
|
import { useAsyncOperation } from '@/core/composables/useAsyncOperation'
|
||||||
|
|
||||||
// Nostr event kinds for market functionality
|
// Nostr event kinds for market functionality
|
||||||
const MARKET_EVENT_KINDS = {
|
const MARKET_EVENT_KINDS = {
|
||||||
|
|
@ -44,9 +45,11 @@ export function useMarket() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Async operations
|
||||||
|
const marketOperation = useAsyncOperation()
|
||||||
|
const connectionOperation = useAsyncOperation()
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const isLoading = ref(false)
|
|
||||||
const error = ref<Error | null>(null)
|
|
||||||
const isConnected = ref(false)
|
const isConnected = ref(false)
|
||||||
const activeMarket = computed(() => marketStore.activeMarket)
|
const activeMarket = computed(() => marketStore.activeMarket)
|
||||||
const markets = computed(() => marketStore.markets)
|
const markets = computed(() => marketStore.markets)
|
||||||
|
|
@ -56,20 +59,15 @@ export function useMarket() {
|
||||||
|
|
||||||
// Connection state
|
// Connection state
|
||||||
const connectionStatus = computed(() => {
|
const connectionStatus = computed(() => {
|
||||||
|
if (connectionOperation.isLoading.value) return 'connecting'
|
||||||
if (isConnected.value) return 'connected'
|
if (isConnected.value) return 'connected'
|
||||||
if (nostrStore.isConnecting) return 'connecting'
|
if (connectionOperation.error.value || nostrStore.error) return 'error'
|
||||||
if (nostrStore.error) return 'error'
|
|
||||||
return 'disconnected'
|
return 'disconnected'
|
||||||
})
|
})
|
||||||
|
|
||||||
// Load market from naddr
|
// Load market from naddr
|
||||||
const loadMarket = async (naddr: string) => {
|
const loadMarket = async (naddr: string) => {
|
||||||
try {
|
return await marketOperation.execute(async () => {
|
||||||
isLoading.value = true
|
|
||||||
error.value = null
|
|
||||||
|
|
||||||
// Load market from naddr
|
|
||||||
|
|
||||||
// Parse naddr to get market data
|
// Parse naddr to get market data
|
||||||
// TODO: Confirm if this should use nostrStore.account?.pubkey or authService.user.value?.pubkey
|
// TODO: Confirm if this should use nostrStore.account?.pubkey or authService.user.value?.pubkey
|
||||||
const marketData = {
|
const marketData = {
|
||||||
|
|
@ -82,13 +80,10 @@ export function useMarket() {
|
||||||
}
|
}
|
||||||
|
|
||||||
await loadMarketData(marketData)
|
await loadMarketData(marketData)
|
||||||
|
return marketData
|
||||||
} catch (err) {
|
}, {
|
||||||
error.value = err instanceof Error ? err : new Error('Failed to load market')
|
errorMessage: 'Failed to load market'
|
||||||
throw err
|
})
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load market data from Nostr events
|
// Load market data from Nostr events
|
||||||
|
|
@ -560,7 +555,7 @@ export function useMarket() {
|
||||||
|
|
||||||
// Connect to market
|
// Connect to market
|
||||||
const connectToMarket = async () => {
|
const connectToMarket = async () => {
|
||||||
try {
|
return await connectionOperation.execute(async () => {
|
||||||
console.log('🛒 Checking RelayHub connection...')
|
console.log('🛒 Checking RelayHub connection...')
|
||||||
// Use existing relay hub connection (should already be connected by base module)
|
// Use existing relay hub connection (should already be connected by base module)
|
||||||
isConnected.value = relayHub.isConnected.value
|
isConnected.value = relayHub.isConnected.value
|
||||||
|
|
@ -602,18 +597,18 @@ export function useMarket() {
|
||||||
|
|
||||||
// Note: Order-related DMs are now handled by chat service forwarding
|
// Note: Order-related DMs are now handled by chat service forwarding
|
||||||
// No need for separate subscription
|
// No need for separate subscription
|
||||||
|
|
||||||
} catch (err) {
|
return { isConnected: isConnected.value }
|
||||||
console.error('🛒 Failed to connect to market:', err)
|
}, {
|
||||||
error.value = err instanceof Error ? err : new Error('Failed to connect to market')
|
errorMessage: 'Failed to connect to market'
|
||||||
throw err
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disconnect from market
|
// Disconnect from market
|
||||||
const disconnectFromMarket = () => {
|
const disconnectFromMarket = () => {
|
||||||
isConnected.value = false
|
isConnected.value = false
|
||||||
error.value = null
|
marketOperation.clear()
|
||||||
|
connectionOperation.clear()
|
||||||
// Market disconnected
|
// Market disconnected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -631,8 +626,10 @@ export function useMarket() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// State
|
// State
|
||||||
isLoading: readonly(isLoading),
|
isLoading: readonly(marketOperation.isLoading),
|
||||||
error: readonly(error),
|
error: readonly(marketOperation.error),
|
||||||
|
isConnecting: readonly(connectionOperation.isLoading),
|
||||||
|
connectionError: readonly(connectionOperation.error),
|
||||||
isConnected: readonly(isConnected),
|
isConnected: readonly(isConnected),
|
||||||
connectionStatus: readonly(connectionStatus),
|
connectionStatus: readonly(connectionStatus),
|
||||||
activeMarket: readonly(activeMarket),
|
activeMarket: readonly(activeMarket),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue