# Best Practices Guide

Comprehensive best practices for building AwesCode UI applications with UI components, vue-mc models, and Laravel backend integration.

## Table of Contents

1. [Component Communication](#component-communication)
2. [Loading States & Progress](#loading-states--progress)
3. [Data Management](#data-management)
4. [Error Handling](#error-handling)
5. [User Feedback](#user-feedback)
6. [Date Formatting](#date-formatting)
7. [Component Imports](#component-imports)
8. [Accessibility](#accessibility)
9. [Performance](#performance)
10. [Styling](#styling)
11. [Table Patterns](#table-patterns)
12. [Navigation](#navigation)

---

## Component Communication

### Event-Driven Architecture

Use events for parent-child communication with specific, descriptive event names.

#### ✅ Good: Specific Events with Clear Data

```markup
<!-- Child Component -->
<script>
export default {
    methods: {
        selectDescription(description) {
            this.$emit('description-selected', description)
        },

        updateTranslations(translations) {
            this.$emit('translations-generated', translations)
        },

        updateStatus(generating, text) {
            this.$emit('ai-status-changed', {
                generating,
                statusText: text
            })
        }
    }
}
</script>

<!-- Parent Component -->
<template>
    <AIAssistant
        @description-selected="handleDescription"
        @translations-generated="handleTranslations"
        @ai-status-changed="handleStatus"
    />
</template>
```

#### ❌ Bad: Generic Events with Unclear Data

```javascript
// ❌ WRONG - Generic event name
this.$emit('change', someData)

// ❌ WRONG - Type discrimination in payload
this.$emit('update', { type: 'description', value: description })

// ❌ WRONG - No data context
this.$emit('done')
```

### Props vs Events

**Props down, events up:**
- Use props to pass data to child components
- Use events to notify parent of changes
- Never mutate props directly in child

```markup
<!-- ✅ GOOD -->
<script>
export default {
    props: {
        value: String
    },

    methods: {
        updateValue(newValue) {
            this.$emit('input', newValue)  // Emit event
        }
    }
}
</script>

<!-- ❌ BAD -->
<script>
export default {
    props: {
        value: String
    },

    methods: {
        updateValue(newValue) {
            this.value = newValue  // ❌ Never mutate props
        }
    }
}
</script>
```

---

## Loading States & Progress

### Button Loading States

Always use the `:loading` prop for AwButton.

#### ✅ Good: Simple Loading State

```markup
<AwButton
    :loading="isLoading"
    @click="handleAction"
    text="Save"
/>

<script>
export default {
    data() {
        return {
            isLoading: false
        }
    },

    methods: {
        async handleAction() {
            this.isLoading = true
            try {
                await this.performAction()
            } finally {
                this.isLoading = false
            }
        }
    }
}
</script>
```

#### ❌ Bad: Conditional Text Inside Button

```markup
<!-- ❌ WRONG - Button handles loading text automatically -->
<AwButton :loading="isLoading">
    <span v-if="isLoading">Loading...</span>
    <span v-else>Save</span>
</AwButton>
```

### Custom Button Styles with Loading

When styling buttons, exclude loading state from custom CSS:

```scss
/* ✅ GOOD - Excludes loading state */
.custom-btn:not([disabled]):not(.loading) {
    background-color: var(--c-accent) !important;
    border-color: var(--c-accent) !important;
}

.custom-btn:hover:not([disabled]):not(.loading) {
    filter: brightness(1.1) !important;
}

/* ❌ BAD - Overrides loading styles */
.custom-btn {
    background: red !important;
}
```

### Model Loading States

Vue-mc models provide automatic loading states:

```markup
<template>
    <AwPageSingle
        :title="pageTitle"
        :action="saveButton"
        @action="save"
    >
        <!-- Form content -->
    </AwPageSingle>
</template>

<script>
export default {
    computed: {
        saveButton() {
            return {
                text: 'Save',
                loading: this.model.saving  // ✅ Automatic from vue-mc
            }
        }
    }
}
</script>
```

### Page Progress Indicator

Use the built-in `progress` prop for long operations:

```markup
<template>
    <AwPageSingle
        :title="pageTitle"
        :action="saveButton"
        :progress="isProcessing ? uploadProgress : null"
        @action="save"
    >
        <!-- Page content -->
    </AwPageSingle>
</template>

<script>
export default {
    data() {
        return {
            uploadProgress: 0,
            isProcessing: false
        }
    },

    computed: {
        saveButton() {
            return {
                key: 'save',
                label: 'Save',
                loading: this.model.saving,
                color: 'accent'
            }
        }
    },

    methods: {
        async handleAction(action) {
            if (action.key === 'save') {
                await this.save()
            }
        }
    }
}
</script>
```

**Note:** For custom status indicators (AI generating, etc.), you can still use the `#buttons` slot alongside the action prop for non-action UI elements.

---

## Data Management

### Collection Initialization

Collections always take 2 arguments: `(models, options)`

#### ✅ Good: Proper Collection Initialization

```javascript
import Customers from '~/collections/Customers'

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

#### ❌ Bad: Missing Options

```javascript
// ❌ WRONG - Missing shop_uuid
customers: new Customers([])

// ❌ WRONG - Only one argument
customers: new Customers()
```

### Model Initialization

Models take 3 arguments: `(attributes, collection, options)`

#### ✅ Good: Proper Model Initialization

```javascript
import Customer from '~/models/Customer'

export default {
    data() {
        return {
            // New model
            customer: new Customer(
                {},  // Empty attributes
                null,  // No parent collection
                { shop_uuid: this.$route.params.shop_uuid }
            ),

            // Existing model
            customer: new Customer(
                { id: this.$route.params.id },  // ID from route
                null,
                { shop_uuid: this.$route.params.shop_uuid }
            )
        }
    }
}
```

#### ❌ Bad: Wrong Number of Arguments

```javascript
// ❌ WRONG - Only one argument
customer: new Customer({ id: 1 })

// ❌ WRONG - Missing options
customer: new Customer({ id: 1 }, null)
```

### Always Include shop_uuid

```javascript
// ✅ GOOD - Shop UUID in options
customers: new Customers([], {
    shop_uuid: this.$route.params.shop_uuid
})

// ❌ BAD - No shop UUID
customers: new Customers([])
```

### Use Standard vue-mc Methods

Don't create custom methods that duplicate vue-mc functionality:

```javascript
// ✅ GOOD - Use standard vue-mc methods
if (this.customers.isEmpty()) {
    // Collection is empty
}

if (Object.keys(this.model.errors).length > 0) {
    // Model has validation errors
}

// ❌ BAD - Custom methods
if (this.customers.hasItems()) {  // ❌ Use isEmpty() instead
}

if (this.model.hasErrors()) {  // ❌ Use Object.keys(errors).length > 0 instead
}
```

### AwTableBuilder Automatically Fetches

Don't manually fetch when using AwTableBuilder:

```markup
<!-- ✅ GOOD - Let AwTableBuilder handle fetching -->
<template>
    <AwTableBuilder :collection="customers">
        <AwTableCol field="name" title="Name" />
    </AwTableBuilder>
</template>

<script>
export default {
    data() {
        return {
            customers: new Customers([], {
                shop_uuid: this.$route.params.shop_uuid
            })
        }
    }
    // No fetch() or mounted() needed!
}
</script>

<!-- ❌ BAD - Manual fetch not needed -->
<script>
export default {
    async mounted() {
        await this.customers.fetch()  // ❌ Unnecessary!
    }
}
</script>
```

---

## Error Handling

### Field-Level Validation

Always bind `:error` prop to model errors:

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

<AwInput
    v-model="model.email"
    label="Email"
    :error="model.errors.email"
    required
/>
```

### Check Errors After Save

Always check for validation errors after saving:

```javascript
async save() {
    try {
        await this.model.save()

        // ✅ GOOD - Check for errors
        if (Object.keys(this.model.errors).length > 0) {
            this.$notify({
                message: 'Please fix validation errors',
                type: 'error'
            })
            return
        }

        // Success path
        this.$notify({
            message: 'Saved successfully',
            type: 'success'
        })
        this.$router.push('/list')
    } catch (error) {
        this.$notify({
            message: 'Failed to save',
            type: 'error'
        })
    }
}
```

### Handle Fetch Errors

Redirect or show error when fetch fails:

```javascript
async mounted() {
    if (!this.model.isNew()) {
        try {
            await this.model.fetch()
        } catch (error) {
            // 404 - Record not found
            if (error.response?.status === 404) {
                this.$notify({
                    message: 'Record not found',
                    type: 'error'
                })
                this.$router.push('/list')
                return
            }

            // Other errors
            this.$notify({
                message: 'Failed to load data',
                type: 'error'
            })
        }
    }
}
```

### Try-Catch for API Calls

Always wrap API calls in try-catch:

```javascript
async performAction() {
    try {
        const { data } = await this.$axios.post('/api/action', this.payload)
        this.handleSuccess(data)
    } catch (error) {
        console.error('Action failed:', error)

        // User-friendly error message
        this.$notify({
            message: error.response?.data?.message || 'Action failed. Please try again.',
            type: 'error'
        })
    }
}
```

---

## User Feedback

### Notifications

Use `$notify` for user feedback:

```javascript
// Success
this.$notify({
    message: 'Customer created successfully',
    type: 'success'
})

// Error
this.$notify({
    message: 'Failed to delete item',
    type: 'error'
})

// Warning
this.$notify({
    message: 'Changes not saved',
    type: 'warning'
})

// Info
this.$notify({
    message: 'Email sent',
    type: 'info'
})
```

### Confirmations

Use `$confirm` for destructive actions:

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

    if (!confirmed) return

    try {
        await this.$axios.delete(`/api/customers/${item.id}`)
        this.$notify({
            message: 'Customer deleted successfully',
            type: 'success'
        })
        this.customers.fetch()
    } catch (error) {
        this.$notify({
            message: 'Failed to delete customer',
            type: 'error'
        })
    }
}
```

### Success Feedback After Actions

Always provide feedback after user actions:

```javascript
// ✅ GOOD - Clear feedback
async save() {
    await this.model.save()

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

    this.$notify({
        message: `Customer ${this.model.isNew() ? 'created' : 'updated'} successfully`,
        type: 'success'
    })

    this.$router.push('/customers')
}

// ❌ BAD - No feedback
async save() {
    await this.model.save()
    this.$router.push('/customers')  // User doesn't know if it succeeded
}
```

---

## Date Formatting

### Always Use $dayjs

Use `$dayjs` in templates (without `this`):

```markup
<!-- ✅ GOOD - Using $dayjs in templates -->
<template>
    <div>
        <!-- Short date -->
        {{ $dayjs(cell.created_at).format('ll') }}

        <!-- Date with time -->
        {{ $dayjs(cell.updated_at).format('lll') }}

        <!-- Month and year -->
        {{ $dayjs(date).format('MMMM YYYY') }}

        <!-- Relative time -->
        {{ $dayjs(cell.created_at).fromNow() }}
    </div>
</template>
```

Use `this.$dayjs` in methods (with `this`):

```javascript
export default {
    methods: {
        // ✅ GOOD - Using this.$dayjs in methods
        formatMonth(monthString) {
            return this.$dayjs(monthString).format('MMMM YYYY')
        },

        isRecent(date) {
            return this.$dayjs(date).isAfter(
                this.$dayjs().subtract(7, 'days')
            )
        }
    }
}
```

### ❌ Bad: Native Date Methods

```markup
<!-- ❌ WRONG - Using native Date -->
{{ new Date(cell.created_at).toLocaleDateString() }}

<!-- ❌ WRONG - Using toLocaleDateString -->
{{ date.toLocaleDateString('en-US', { year: 'numeric', month: 'short' }) }}
```

### Common Date Formats

| Format | Code | Example |
|--------|------|---------|
| Short date | `$dayjs(date).format('ll')` | Jan 15, 2024 |
| Date with time | `$dayjs(date).format('lll')` | Jan 15, 2024 10:30 AM |
| Long date | `$dayjs(date).format('LL')` | January 15, 2024 |
| Month and year | `$dayjs(date).format('MMMM YYYY')` | January 2024 |
| Short month/year | `$dayjs(date).format('MMM YYYY')` | Jan 2024 |
| Relative time | `$dayjs(date).fromNow()` | 2 hours ago |

---

## Component Imports

### Auto-Imported Components

Atoms and molecules are globally registered:

```markup
<!-- ✅ GOOD - No import needed for global components -->
<template>
    <div>
        <AwButton text="Click me" />
        <AwInput v-model="value" />
        <AwCard title="Card Title">
            Content
        </AwCard>
    </div>
</template>

<!-- No imports needed! -->
```

### Manual Imports

Collections, models, and utilities must be imported:

```markup
<script>
// ✅ GOOD - Import collections and models
import Customers from '~/collections/Customers'
import Customer from '~/models/Customer'

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

### Components from /components/

Components in your project's `/components/` folder are auto-imported by Nuxt:

```markup
<!-- ✅ GOOD - Auto-imported from /components/ -->
<template>
    <div>
        <CustomerCard :customer="customer" />
        <OrderSummary :order="order" />
    </div>
</template>

<!-- No imports needed for project components -->
```

---

## Accessibility

### Semantic HTML

Use semantic HTML elements:

```markup
<!-- ✅ GOOD - Semantic HTML -->
<nav>
    <ul>
        <li><a href="/home">Home</a></li>
        <li><a href="/about">About</a></li>
    </ul>
</nav>

<!-- ❌ BAD - Non-semantic -->
<div class="nav">
    <div class="nav-item">Home</div>
    <div class="nav-item">About</div>
</div>
```

### ARIA Labels

Provide ARIA labels for interactive elements:

```markup
<!-- ✅ GOOD - ARIA labels -->
<AwButton
    @click="deleteItem"
    icon="awesio/delete"
    aria-label="Delete customer"
/>

<AwInput
    v-model="search"
    placeholder="Search..."
    aria-label="Search customers"
/>
```

### Keyboard Navigation

Ensure keyboard navigation works:

```markup
<AwButton
    @click="handleAction"
    @keydown.enter="handleAction"
    @keydown.space.prevent="handleAction"
>
    Action
</AwButton>
```

---

## Performance

### Computed Properties Over Watchers

Use computed properties for derived state:

```javascript
// ✅ GOOD - Computed property
export default {
    computed: {
        fullName() {
            return `${this.firstName} ${this.lastName}`
        },

        hasErrors() {
            return Object.keys(this.model.errors).length > 0
        }
    }
}

// ❌ BAD - Watcher for derived state
export default {
    data() {
        return {
            fullName: ''
        }
    },

    watch: {
        firstName() {
            this.fullName = `${this.firstName} ${this.lastName}`
        },
        lastName() {
            this.fullName = `${this.firstName} ${this.lastName}`
        }
    }
}
```

### Proper Key Attributes

Always use unique keys in v-for:

```markup
<!-- ✅ GOOD - Unique key -->
<div v-for="customer in customers" :key="customer.id">
    {{ customer.name }}
</div>

<!-- ❌ BAD - Index as key -->
<div v-for="(customer, index) in customers" :key="index">
    {{ customer.name }}
</div>

<!-- ❌ BAD - No key -->
<div v-for="customer in customers">
    {{ customer.name }}
</div>
```

### Efficient Re-rendering

Avoid unnecessary re-renders:

```javascript
// ✅ GOOD - Computed property caches result
computed: {
    expensiveComputation() {
        return this.items.filter(item => {
            // Complex filtering logic
        })
    }
}

// ❌ BAD - Method recalculates every render
methods: {
    expensiveComputation() {
        return this.items.filter(item => {
            // Complex filtering logic
        })
    }
}
```

---

## Styling

### CSS Custom Properties

Use CSS custom properties for theming:

```scss
/* ✅ GOOD - CSS custom properties */
.button-accent {
    background-color: var(--c-accent);
    border-color: var(--c-accent);
    color: var(--c-on-accent);
}

.button-accent:hover {
    filter: brightness(1.1);
}

/* ❌ BAD - Hardcoded colors */
.button-accent {
    background-color: #6366f1;
    border-color: #6366f1;
    color: white;
}
```

### Responsive Design

Use AwGrid component for responsive layouts:

```markup
<!-- ✅ GOOD - Mobile-first responsive with AwGrid -->
<AwGrid :col="{ md: 2, lg: 4 }">
    <AwCard>Card 1</AwCard>
    <AwCard>Card 2</AwCard>
    <AwCard>Card 3</AwCard>
    <AwCard>Card 4</AwCard>
</AwGrid>
```

### Hover Effects

Add appropriate hover states:

```scss
/* ✅ GOOD - Hover effects */
.card:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    transition: all 0.2s ease;
}

.link:hover {
    color: var(--c-accent);
    text-decoration: underline;
}
```

---

## Table Patterns

### Understanding field Prop

**With field:** `cell` = field value only
**Without field:** `cell` = entire row object

```markup
<!-- WITH field: cell is just the value -->
<AwTableCol title="Name" field="name">
    <template #default="{ cell }">
        {{ cell }} <!-- cell is the string value of 'name' -->
    </template>
</AwTableCol>

<!-- WITHOUT field: cell is full object -->
<AwTableCol title="Name">
    <template #default="{ cell }">
        {{ cell.name }} <!-- cell is the full row object -->
        {{ cell.email }} <!-- can access other fields -->
    </template>
</AwTableCol>
```

### Can't Use @click:row and #dropdown Together

```markup
<!-- ❌ BAD - Conflict! -->
<AwTableBuilder
    :collection="items"
    @click:row="viewItem"
>
    <template #dropdown="{ cell }">
        <AwDropdownButton text="Edit" />
    </template>
</AwTableBuilder>

<!-- ✅ GOOD - Choose one approach -->
<AwTableBuilder :collection="items">
    <template #dropdown="{ cell }">
        <AwDropdownButton text="View" @click="viewItem(cell)" />
        <AwDropdownButton text="Edit" @click="editItem(cell)" />
    </template>
</AwTableBuilder>
```

### AwDropdownButton Usage

```markup
<template #dropdown="{ cell }">
    <AwDropdownButton
        text="View"
        @click="viewItem(cell)"
    />
    <AwDropdownButton
        text="Edit"
        @click="editItem(cell)"
    />
    <AwDropdownButton
        text="Delete"
        color="error"
        @click="deleteItem(cell)"
    />
</template>
```

---

## Navigation

### Always Include shop_uuid in Routes

```javascript
// ✅ GOOD - Include shop_uuid
this.$router.push({
    name: 'customers-edit',
    params: {
        shop_uuid: this.$route.params.shop_uuid,
        id: customer.id
    }
})

// ❌ BAD - Missing shop_uuid
this.$router.push(`/customers/${customer.id}/edit`)
```

### AwButton href vs @click

Use `href` for navigation, `@click` for actions:

```markup
<!-- ✅ GOOD - href for navigation -->
<AwButton
    :href="`/${shopUuid}/customers/${customer.id}`"
    text="View Customer"
/>

<!-- ✅ GOOD - @click for actions -->
<AwButton
    @click="deleteCustomer(customer)"
    text="Delete"
    color="error"
/>
```

### Mobile Breadcrumbs

Add breadcrumbs for mobile navigation:

```markup
<template>
    <AwPage title="Email Templates">
        <template #mobile-breadcrumbs>
            <AwButton
                :href="`/${$route.params.shop_uuid}/notifications`"
                theme="text"
                icon="arrow-left"
                text="Back to Notifications"
            />
        </template>

        <!-- Page content -->
    </AwPage>
</template>
```

---

## Summary Checklist

### Data Management
- ✅ Collections: 2 arguments `(models, options)`
- ✅ Models: 3 arguments `(attributes, collection, options)`
- ✅ Always include `shop_uuid` in options
- ✅ Use standard vue-mc methods (`.isEmpty()`, `.isNew()`)
- ✅ Let AwTableBuilder handle fetching (no manual `fetch()`)

### Error Handling
- ✅ Bind `:error` to `model.errors.field`
- ✅ Check `Object.keys(model.errors).length > 0` after save
- ✅ Wrap API calls in try-catch
- ✅ Redirect on fetch errors (404)

### User Feedback
- ✅ Use `$notify` for success/error messages
- ✅ Use `$confirm` for destructive actions
- ✅ Show loading states (`:loading` prop)

### Date Formatting
- ✅ Always use `$dayjs` in templates
- ✅ Always use `this.$dayjs` in methods
- ✅ Never use native Date methods

### Navigation
- ✅ Always include `shop_uuid` in routes
- ✅ Use `href` for navigation, `@click` for actions
- ✅ Add mobile breadcrumbs for subpages

## See Also

- [Page Patterns](./page-patterns/) - List, detail, and dashboard patterns
- [Forms Guide](./forms-guide.md) - Form validation and submission
- [Data Fetching Guide](./data-fetching.md) - Working with collections
- [Error Handling Guide](./error-handling.md) - Error patterns
