# Error Handling Guide

Complete guide to handling errors in AwesCode UI applications, from field-level validation to API failures.

## Overview

Error handling in AwesCode UI spans multiple layers:
- **Field-level validation** - Real-time input validation
- **Form-level validation** - Server-side validation after submit
- **API errors** - HTTP errors (404, 403, 500, etc.)
- **Network errors** - Connection failures
- **User feedback** - Notifications and confirmations

## Field-Level Validation

### Basic Field Errors

Use the `:error` prop to display validation errors:

```markup
<template>
    <AwForm>
        <AwInput
            v-model="customer.name"
            :error="customer.errors.name"
            label="Name"
        />

        <AwInput
            v-model="customer.email"
            :error="customer.errors.email"
            label="Email"
            type="email"
        />
    </AwForm>
</template>

<script>
import Customer from '~/models/Customer'

export default {
    data() {
        return {
            customer: new Customer(
                {},
                null,
                { shop_uuid: this.$route.params.shop_uuid }
            )
        }
    }
}
</script>
```

**How it works:**
1. ✅ User submits form
2. ✅ Backend validates and returns errors
3. ✅ Vue-mc populates `model.errors`
4. ✅ `:error` prop displays error message
5. ✅ Error clears when user edits field

### Multiple Error Messages

Some fields may have multiple validation errors:

```markup
<AwPassword
    v-model="user.password"
    :error="user.errors.password"
    label="Password"
/>
```

Backend response:
```json
{
    "errors": {
        "password": [
            "The password must be at least 8 characters.",
            "The password must contain at least one uppercase letter."
        ]
    }
}
```

Vue-mc automatically joins multiple errors with line breaks.

### Nested Property Errors

For nested properties, use bracket notation:

```markup
<AwInput
    v-model="customer.address.street"
    :error="customer.errors['address.street']"
    label="Street"
/>

<AwInput
    v-model="customer.address.city"
    :error="customer.errors['address.city']"
    label="City"
/>
```

Backend response:
```json
{
    "errors": {
        "address.street": ["The street field is required."],
        "address.city": ["The city field is required."]
    }
}
```

### Custom Validation Rules

Add client-side validation before server validation:

```markup
<template>
    <AwInput
        v-model="customer.email"
        :error="emailError"
        label="Email"
        @input="validateEmail"
    />
</template>

<script>
export default {
    data() {
        return {
            emailError: null
        }
    },

    methods: {
        validateEmail() {
            const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/

            if (!this.customer.email) {
                this.emailError = 'Email is required'
                return false
            }

            if (!emailRegex.test(this.customer.email)) {
                this.emailError = 'Invalid email format'
                return false
            }

            this.emailError = null
            return true
        },

        async submit() {
            // Validate before submit
            if (!this.validateEmail()) {
                return
            }

            // Submit to server
            await this.customer.save()
        }
    }
}
</script>
```

### Conditional Field Errors

Show errors only after field has been touched:

```markup
<template>
    <AwInput
        v-model="form.name"
        :error="touchedFields.name && form.errors.name"
        label="Name"
        @blur="touchedFields.name = true"
    />
</template>

<script>
export default {
    data() {
        return {
            touchedFields: {
                name: false,
                email: false
            }
        }
    }
}
</script>
```

## Form-Level Validation

### Check for Errors After Save

After saving, check if server returned validation errors:

```markup
<script>
export default {
    methods: {
        async submit() {
            try {
                await this.customer.save()

                // Check if save returned validation errors
                if (Object.keys(this.customer.errors).length > 0) {
                    this.$notify({
                        message: 'Please fix validation errors',
                        type: 'error'
                    })
                    return
                }

                // Success
                this.$notify({
                    message: 'Customer saved',
                    type: 'success'
                })

                this.$router.push(`/shops/${this.$route.params.shop_uuid}/customers`)
            } catch (error) {
                // Handle non-validation errors
                this.$notify({
                    message: 'Failed to save customer',
                    type: 'error'
                })
            }
        }
    }
}
</script>
```

**Important:**
- ✅ `model.save()` does NOT throw on validation errors
- ✅ Check `Object.keys(model.errors).length > 0` after save
- ✅ Only throw for actual API failures

### Display All Form Errors

Show summary of all validation errors:

```markup
<template>
    <AwForm>
        <!-- Error summary -->
        <AwAlert
            v-if="Object.keys(customer.errors).length > 0"
            type="error"
            class="mb-6"
        >
            <p class="font-medium mb-2">Please fix the following errors:</p>
            <ul class="list-disc list-inside">
                <li v-for="(messages, field) in customer.errors" :key="field">
                    <strong>{{ field }}:</strong> {{ Array.isArray(messages) ? messages.join(', ') : messages }}
                </li>
            </ul>
        </AwAlert>

        <!-- Form fields -->
        <AwInput
            v-model="customer.name"
            :error="customer.errors.name"
            label="Name"
        />
    </AwForm>
</template>
```

### Clear Errors Manually

Clear errors before retrying:

```javascript
methods: {
    async submit() {
        // Clear previous errors
        this.customer.setErrors({})

        await this.customer.save()

        if (Object.keys(this.customer.errors).length > 0) {
            // New errors appeared
            return
        }

        // Success
    },

    resetForm() {
        // Clear all errors
        this.customer.setErrors({})

        // Reset to initial state
        this.customer.reset()
    }
}
```

## API Error Handling

### HTTP Status Codes

Handle different HTTP status codes appropriately:

```javascript
async loadData() {
    try {
        await this.collection.fetch()
    } catch (error) {
        console.error('Fetch failed:', error)

        // Network error (no response)
        if (!error.response) {
            this.$notify({
                message: 'Network error. Please check your connection.',
                type: 'error'
            })
            return
        }

        const status = error.response.status

        // 401 - Unauthorized (handled by nuxt-auth automatically)
        if (status === 401) {
            // User will be redirected to login
            return
        }

        // 403 - Forbidden
        if (status === 403) {
            this.$notify({
                message: 'You do not have permission to view this',
                type: 'error'
            })
            this.$router.push('/dashboard')
            return
        }

        // 404 - Not Found
        if (status === 404) {
            this.$notify({
                message: 'Resource not found',
                type: 'error'
            })
            this.$router.push('/dashboard')
            return
        }

        // 422 - Validation Error (handled by vue-mc)
        if (status === 422) {
            // Validation errors in model.errors
            return
        }

        // 500 - Server Error
        if (status >= 500) {
            this.$notify({
                message: 'Server error. Please try again later.',
                type: 'error'
            })
            return
        }

        // Generic error
        this.$notify({
            message: 'An error occurred. Please try again.',
            type: 'error'
        })
    }
}
```

### Model Fetch Errors

Handle errors when fetching individual models:

```javascript
async mounted() {
    if (!this.customer.isNew()) {
        this.loading = true

        try {
            await this.customer.fetch()
        } catch (error) {
            // 404 - Customer not found
            if (error.response?.status === 404) {
                this.$notify({
                    message: 'Customer not found',
                    type: 'error'
                })
                this.$router.push(`/shops/${this.$route.params.shop_uuid}/customers`)
                return
            }

            // Other errors
            this.$notify({
                message: 'Failed to load customer',
                type: 'error'
            })
        } finally {
            this.loading = false
        }
    }
}
```

### Collection Fetch Errors

Handle errors when fetching collections:

```javascript
async loadCustomers() {
    try {
        await this.customers.fetch()
    } catch (error) {
        // Network error
        if (!error.response) {
            this.$notify({
                message: 'Network error. Please check your connection.',
                type: 'error'
            })
            return
        }

        // Show error message from backend
        const message = error.response?.data?.message || 'Failed to load customers'

        this.$notify({
            message,
            type: 'error'
        })
    }
}
```

### Delete Errors

Handle errors when deleting:

```javascript
async deleteCustomer(customer) {
    const confirmed = await this.$confirm({
        title: 'Delete Customer',
        message: `Are you sure you want to delete ${customer.name}?`
    })

    if (!confirmed) return

    try {
        await this.$axios.delete(`/api/shops/${this.$route.params.shop_uuid}/customers/${customer.id}`)

        this.$notify({
            message: 'Customer deleted',
            type: 'success'
        })

        // Refresh list
        await this.customers.fetch()
    } catch (error) {
        // 404 - Already deleted
        if (error.response?.status === 404) {
            this.$notify({
                message: 'Customer already deleted',
                type: 'warning'
            })
            await this.customers.fetch()
            return
        }

        // 409 - Conflict (has related records)
        if (error.response?.status === 409) {
            this.$notify({
                message: 'Cannot delete customer with existing orders',
                type: 'error'
            })
            return
        }

        // Generic error
        this.$notify({
            message: 'Failed to delete customer',
            type: 'error'
        })
    }
}
```

## User Feedback

### Success Notifications

Show success feedback for important actions:

```javascript
// After save
this.$notify({
    message: 'Customer saved successfully',
    type: 'success'
})

// After delete
this.$notify({
    message: 'Customer deleted',
    type: 'success'
})

// After bulk action
this.$notify({
    message: `${count} customers updated`,
    type: 'success'
})
```

### Error Notifications

Show error feedback with context:

```javascript
// Generic error
this.$notify({
    message: 'Failed to save customer',
    type: 'error'
})

// Specific error
this.$notify({
    message: 'Email address already exists',
    type: 'error'
})

// Error with action suggestion
this.$notify({
    message: 'Connection lost. Please check your internet and try again.',
    type: 'error'
})
```

### Warning Notifications

Show warnings for non-critical issues:

```javascript
// Partial success
this.$notify({
    message: 'Some items could not be processed',
    type: 'warning'
})

// Data inconsistency
this.$notify({
    message: 'Data may be outdated. Refresh to see latest.',
    type: 'warning'
})
```

### Confirmation Dialogs

Confirm destructive actions:

```javascript
async deleteCustomer(customer) {
    const confirmed = await this.$confirm({
        title: 'Delete Customer',
        message: `Are you sure you want to delete ${customer.name}? This action cannot be undone.`
    })

    if (!confirmed) {
        // User cancelled
        return
    }

    // Proceed with delete
    await this.$axios.delete(`/api/customers/${customer.id}`)
}
```

**With custom button text:**

```javascript
const confirmed = await this.$confirm({
    title: 'Permanent Delete',
    message: 'This will permanently delete all data. Are you absolutely sure?',
    confirmText: 'Yes, Delete Everything',
    cancelText: 'Cancel'
})
```

## Graceful Degradation

### Fallback Values

Provide fallback values for missing data:

```markup
<template>
    <div>
        <!-- Safe access with fallback -->
        <h1>{{ customer.name || 'Unnamed Customer' }}</h1>

        <!-- Optional chaining with fallback -->
        <p>{{ customer.address?.city || 'No city' }}</p>

        <!-- Array with fallback -->
        <div v-if="orders.models.length > 0">
            <div v-for="order in orders.models" :key="order.id">
                {{ order.number }}
            </div>
        </div>
        <div v-else>
            <p class="text-gray-500">No orders yet</p>
        </div>
    </div>
</template>
```

### Default Data

Provide default data structure:

```javascript
data() {
    return {
        customer: new Customer({
            name: '',
            email: '',
            phone: '',
            address: {
                street: '',
                city: '',
                zip: ''
            }
        }, null, {
            shop_uuid: this.$route.params.shop_uuid
        })
    }
}
```

### Loading States

Show appropriate loading states:

```markup
<template>
    <div>
        <!-- Loading state -->
        <div v-if="loading" class="py-12 text-center">
            <AwProgress indeterminate />
        </div>

        <!-- Error state -->
        <div v-else-if="error" class="py-12 text-center">
            <AwEmptyContainer
                icon="alert-circle"
                title="Failed to Load"
                :description="error"
            >
                <AwButton @click="retry">
                    Try Again
                </AwButton>
            </AwEmptyContainer>
        </div>

        <!-- Success state -->
        <div v-else>
            <!-- Content -->
        </div>
    </div>
</template>

<script>
export default {
    data() {
        return {
            loading: true,
            error: null
        }
    },

    async mounted() {
        await this.loadData()
    },

    methods: {
        async loadData() {
            this.loading = true
            this.error = null

            try {
                await this.collection.fetch()
            } catch (error) {
                this.error = error.response?.data?.message || 'Failed to load data'
            } finally {
                this.loading = false
            }
        },

        async retry() {
            await this.loadData()
        }
    }
}
</script>
```

### Empty States

Show helpful empty states:

```markup
<template>
    <AwTableBuilder :collection="customers">
        <AwTableCol field="name" title="Name" />

        <!-- Custom empty state -->
        <template #empty>
            <AwEmptyContainer
                icon="users"
                title="No Customers Yet"
                description="Get started by adding your first customer."
            >
                <AwButton
                    :href="`/shops/${$route.params.shop_uuid}/customers/new`"
                    color="accent"
                >
                    Add Customer
                </AwButton>
            </AwEmptyContainer>
        </template>
    </AwTableBuilder>
</template>
```

## Error Recovery

### Retry Logic

Implement retry for transient failures:

```javascript
async fetchWithRetry(maxRetries = 3) {
    let lastError

    for (let i = 0; i < maxRetries; i++) {
        try {
            await this.collection.fetch()
            return // Success
        } catch (error) {
            lastError = error

            // Network error - retry
            if (!error.response) {
                await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)))
                continue
            }

            // Server error - retry
            if (error.response.status >= 500) {
                await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)))
                continue
            }

            // Client error - don't retry
            throw error
        }
    }

    // All retries failed
    throw lastError
}
```

### Offline Detection

Handle offline scenarios:

```javascript
mounted() {
    // Check if online
    if (!navigator.onLine) {
        this.$notify({
            message: 'You are offline. Please check your connection.',
            type: 'error'
        })
        return
    }

    // Listen for offline events
    window.addEventListener('offline', this.handleOffline)
    window.addEventListener('online', this.handleOnline)
},

beforeDestroy() {
    window.removeEventListener('offline', this.handleOffline)
    window.removeEventListener('online', this.handleOnline)
},

methods: {
    handleOffline() {
        this.$notify({
            message: 'Connection lost',
            type: 'warning'
        })
    },

    handleOnline() {
        this.$notify({
            message: 'Connection restored',
            type: 'success'
        })

        // Retry failed requests
        this.retryFailedRequests()
    }
}
```

### Stale Data Warning

Warn users about stale data:

```javascript
data() {
    return {
        lastFetchTime: null,
        staleThreshold: 5 * 60 * 1000 // 5 minutes
    }
},

computed: {
    isDataStale() {
        if (!this.lastFetchTime) return false
        return Date.now() - this.lastFetchTime > this.staleThreshold
    }
},

methods: {
    async loadData() {
        await this.collection.fetch()
        this.lastFetchTime = Date.now()
    }
}
```

```markup
<template>
    <div>
        <AwAlert v-if="isDataStale" type="warning" class="mb-6">
            Data may be outdated.
            <AwButton size="sm" @click="loadData">
                Refresh
            </AwButton>
        </AwAlert>

        <!-- Content -->
    </div>
</template>
```

## Complete Examples

### List Page with Error Handling

```markup
<template>
    <AwPage title="Customers">
        <!-- Error state -->
        <AwAlert v-if="fetchError" type="error" class="mb-6">
            {{ fetchError }}
            <AwButton size="sm" @click="retry">
                Try Again
            </AwButton>
        </AwAlert>

        <!-- Table -->
        <AwTableBuilder
            :collection="customers"
            @click:row="viewCustomer"
        >
            <AwTableCol field="name" title="Name" />
            <AwTableCol field="email" title="Email" />
        </AwTableBuilder>
    </AwPage>
</template>

<script>
import Customers from '~/collections/Customers'

export default {
    data() {
        return {
            customers: new Customers([], {
                shop_uuid: this.$route.params.shop_uuid
            }),
            fetchError: null
        }
    },

    methods: {
        viewCustomer(customer) {
            this.$router.push(`/shops/${this.$route.params.shop_uuid}/customers/${customer.id}`)
        },

        async retry() {
            this.fetchError = null
            try {
                await this.customers.fetch()
            } catch (error) {
                this.fetchError = error.response?.data?.message || 'Failed to load customers'
            }
        }
    }
}
</script>
```

### Detail Page with Error Handling

```markup
<template>
    <AwPageSingle
        hide-menu
        :title="customer.isNew() ? 'New Customer' : 'Edit Customer'"
    >
        <!-- Loading state -->
        <div v-if="loading" class="py-12 text-center">
            <AwProgress indeterminate />
        </div>

        <!-- Form -->
        <AwCard v-else title="Customer Information">
            <!-- Error summary -->
            <AwAlert
                v-if="Object.keys(customer.errors).length > 0"
                type="error"
                class="mb-6"
            >
                Please fix validation errors below
            </AwAlert>

            <AwInput
                v-model="customer.name"
                :error="customer.errors.name"
                label="Name"
            />

            <AwInput
                v-model="customer.email"
                :error="customer.errors.email"
                label="Email"
                type="email"
            />
        </AwCard>

        <!-- Actions -->
        <template #buttons>
            <AwButton
                :loading="saving"
                color="accent"
                @click="save"
            >
                Save
            </AwButton>
        </template>
    </AwPageSingle>
</template>

<script>
import Customer from '~/models/Customer'

export default {
    data() {
        const uuid = this.$route.params.uuid

        return {
            customer: new Customer(
                uuid === 'new' ? {} : { id: uuid },
                null,
                { shop_uuid: this.$route.params.shop_uuid }
            ),
            loading: uuid !== 'new',
            saving: false
        }
    },

    async mounted() {
        if (!this.customer.isNew()) {
            try {
                await this.customer.fetch()
            } catch (error) {
                if (error.response?.status === 404) {
                    this.$notify({
                        message: 'Customer not found',
                        type: 'error'
                    })
                } else {
                    this.$notify({
                        message: 'Failed to load customer',
                        type: 'error'
                    })
                }

                this.$router.push(`/shops/${this.$route.params.shop_uuid}/customers`)
            } finally {
                this.loading = false
            }
        }
    },

    methods: {
        async save() {
            this.saving = true

            try {
                await this.customer.save()

                if (Object.keys(this.customer.errors).length > 0) {
                    this.$notify({
                        message: 'Please fix validation errors',
                        type: 'error'
                    })
                    return
                }

                this.$notify({
                    message: 'Customer saved',
                    type: 'success'
                })

                this.$router.push(`/shops/${this.$route.params.shop_uuid}/customers`)
            } catch (error) {
                this.$notify({
                    message: 'Failed to save customer',
                    type: 'error'
                })
            } finally {
                this.saving = false
            }
        }
    }
}
</script>
```

## Best Practices

### 1. Always Handle Errors

```javascript
// ✅ GOOD
try {
    await this.model.save()
} catch (error) {
    this.$notify({
        message: 'Failed to save',
        type: 'error'
    })
}

// ❌ BAD
await this.model.save()  // No error handling
```

### 2. Check Validation Errors After Save

```javascript
// ✅ GOOD
await this.model.save()
if (Object.keys(this.model.errors).length > 0) {
    // Handle validation errors
    return
}

// ❌ BAD
await this.model.save()
// Assumes success
```

### 3. Provide User Feedback

```javascript
// ✅ GOOD
try {
    await this.model.save()
    this.$notify({
        message: 'Saved successfully',
        type: 'success'
    })
} catch (error) {
    this.$notify({
        message: 'Failed to save',
        type: 'error'
    })
}

// ❌ BAD
try {
    await this.model.save()
} catch (error) {
    console.error(error)  // Silent failure
}
```

### 4. Handle Different Error Types

```javascript
// ✅ GOOD
catch (error) {
    if (!error.response) {
        // Network error
    } else if (error.response.status === 404) {
        // Not found
    } else {
        // Other error
    }
}

// ❌ BAD
catch (error) {
    // Generic error handling for all cases
}
```

### 5. Confirm Destructive Actions

```javascript
// ✅ GOOD
const confirmed = await this.$confirm({
    title: 'Delete Customer',
    message: 'Are you sure?'
})
if (!confirmed) return

// ❌ BAD
// Delete without confirmation
```

### 6. Provide Fallback Values

```javascript
// ✅ GOOD
{{ customer.name || 'Unnamed' }}

// ❌ BAD
{{ customer.name }}  // May show empty
```

### 7. Show Loading States

```javascript
// ✅ GOOD
<div v-if="loading">Loading...</div>
<div v-else>Content</div>

// ❌ BAD
<div>{{ data }}</div>  // May show undefined
```

## See Also

- [Data Fetching Guide](./data-fetching.md) - Fetch patterns and error handling
- [Best Practices Guide](./best-practices.md) - General best practices
- [Forms Guide](./forms-guide.md) - Form validation patterns
- [Vue-MC Documentation](../../vue-mc/docs/) - Model and collection error handling
