Add phase tracking, TypeScript requirement, and Vuexy theme reference docs

Update CLAUDE.md with mandatory phase tracking rules (GitHub issues + TASKS.md),
TypeScript requirement for all frontend code, Vuexy theme reference, and updated
project structure reflecting Phase 1-2 completion. Add comprehensive CLAUDE.md
to Vuexy theme directory documenting components, patterns, and conventions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Dev
2026-02-09 08:00:28 -05:00
parent 14df9fde60
commit 0fe4e4ab42
2 changed files with 806 additions and 25 deletions

161
CLAUDE.md
View File

@@ -3,33 +3,59 @@
## Project
EZSCALE Site — Laravel 12 application replacing WHMCS for VPS/Dedicated Server hosting management (billing, subscriptions, provisioning, customer management, SSO).
## Phase Tracking (MANDATORY)
All work MUST be tracked against GitHub issues and `TASKS.md`:
1. **Before starting any phase work:** Check the relevant GitHub issue (see `gh issue list`) and `TASKS.md` for current status.
2. **While working:** Update the GitHub issue with progress comments as significant milestones are completed (e.g., `gh issue comment <number> --body "Completed X"`).
3. **After completing work:** Update `TASKS.md` to check off completed items and close/update the corresponding GitHub issue.
4. **New tasks discovered during work:** Create sub-issues or add items to `TASKS.md` immediately.
5. **Commit messages:** Reference the GitHub issue number (e.g., `Fix checkout validation (#3)`).
GitHub issues: https://github.com/EZSCALE/accounting/issues
## Laravel App Location
The Laravel application is in **`website/`**. All artisan, composer, and npm commands run from there. This is currently a **base Laravel 12 install** — no additional packages or custom code added yet.
The Laravel application is in **`website/`**. All artisan, composer, and npm commands run from there.
```
website/ # Laravel 12 base install
├── app/ # Models, Http (Controllers), Providers
├── bootstrap/app.php # Middleware, exceptions, routing (Laravel 12 style)
├── config/ # Standard Laravel config files
├── database/migrations/ # Default user, cache, jobs migrations only
├── resources/ # Views, JS, CSS
├── routes/ # web.php, console.php
├── tests/ # Pest 4 test suite
├── composer.json # PHP 8.2+, Laravel 12, Pest 4, Pint
├── package.json # Vite 7, Tailwind CSS 4, Axios
website/
├── app/
│ ├── Models/ # 14 Eloquent models
│ ├── Http/Controllers/ # Account/ and Admin/ controllers
│ ├── Services/Billing/ # BillingServiceInterface, Stripe, PayPal, Dunning
│ ├── Events/ # PaymentSucceeded/Failed, SubscriptionCreated/Cancelled
│ ├── Listeners/ # HandlePaymentSucceeded/Failed
│ └── Providers/ # AppServiceProvider, FortifyServiceProvider
├── bootstrap/app.php # Middleware, exceptions, routing (Laravel 12 style)
├── config/ # App, auth, fortify, passport, cashier, paypal, permission
├── database/
│ ├── migrations/ # 30 migrations (15 custom + defaults + packages)
│ ├── factories/ # 7 factories
│ └── seeders/ # Roles, plans, admin user
├── resources/
│ ├── js/ # Vue 3 + Inertia pages (TO BE MIGRATED TO TypeScript)
│ │ ├── Layouts/ # AppLayout, AuthLayout, AdminLayout
│ │ ├── Pages/ # Auth/, Billing/, Plans/, Subscriptions/, Admin/
│ │ └── Components/ # Card, Button, NavLink, FlashMessages
│ ├── css/app.css # Tailwind CSS 4
│ └── views/app.blade.php # Inertia root template
├── routes/ # web.php, account.php, admin.php, marketing.php, webhooks.php, api.php
├── tests/ # 53 Pest tests (Phase 1 + Phase 2)
├── composer.json
├── package.json
└── vite.config.js
```
## Tech Stack
- **Framework:** Laravel 12 (PHP 8.2+), Laravel 12 slim structure (no Kernel files)
- **Frontend:** Tailwind CSS 4 + Vite 7 (Vue 3 + Inertia.js to be added)
- **UI Theme:** Vuexy VueJS + Laravel Admin Dashboard Template (to be integrated)
- **Framework:** Laravel 12 (PHP 8.3), Laravel 12 slim structure (no Kernel files)
- **Frontend:** Vue 3 + Inertia.js v2 + TypeScript (REQUIRED) + Tailwind CSS 4 + Vite 7
- **UI Theme:** Vuexy Vue + Laravel Admin Dashboard (reference at `../vuexy-theme-vue-laravel-full-example-typescript/`)
- **Testing:** Pest 4 + PHPUnit 12
- **Formatting:** Laravel Pint
- **Payments:** Laravel Cashier Stripe + srmklive/laravel-paypal (to be installed)
- **Payments:** Laravel Cashier (Stripe) + srmklive/paypal (PayPal)
- **Database:** MySQL 8.x, Redis for cache/queue/sessions
- **Auth:** Laravel Fortify + Passport (to be installed)
- **Roles:** spatie/laravel-permission (to be installed)
- **Auth:** Laravel Fortify (login, register, 2FA, password reset, email verify) + Passport (OAuth2/SSO)
- **Roles:** spatie/laravel-permission (admin, customer)
## Commands
```bash
@@ -40,10 +66,85 @@ php artisan test --compact # Run Pest tests
php artisan migrate # Run migrations
npm run dev # Vite dev server only
npm run build # Production build
vendor/bin/pint --dirty # Format changed files
vendor/bin/pint --dirty --format agent # Format changed files
```
## TypeScript Requirement (MANDATORY)
**All frontend code MUST use TypeScript.** This applies to all Vue components, composables, utilities, and type definitions.
### Rules
- All `.vue` files must use `<script setup lang="ts">` (never plain `<script setup>`)
- Define component props with `interface Props` and `withDefaults(defineProps<Props>(), {...})`
- Define emits with typed `defineEmits<{ event: [payload: Type] }>()`
- Use explicit types for `ref<Type>()`, `computed<Type>()`, and function return types
- No `any` type — use proper interfaces or type aliases
- Shared types go in `resources/js/types/` (e.g., `models.ts`, `billing.ts`)
- Inertia page props should be typed with interfaces
### Example Component Pattern
```vue
<script setup lang="ts">
import { Link } from '@inertiajs/vue3'
import AppLayout from '@/Layouts/AppLayout.vue'
interface Props {
title: string
items: Item[]
count?: number
}
interface Item {
id: number
name: string
status: 'active' | 'inactive'
}
defineOptions({ layout: AppLayout })
const props = withDefaults(defineProps<Props>(), {
count: 0,
})
const isActive = computed<boolean>(() => props.count > 0)
</script>
```
### Migration Note
Existing `.vue` files in `resources/js/` were written in plain JavaScript during Phase 1-2. They must be migrated to TypeScript as they are touched. When working on a page, convert it to TypeScript as part of the work.
## Vuexy Theme Reference
The Vuexy theme is at `../vuexy-theme-vue-laravel-full-example-typescript/`. Use it as a **reference** for UI patterns, component design, and styling conventions.
### Key Vuexy Patterns to Follow
- **Component structure:** `<script lang="ts" setup>` → props interface → state → computed → methods → watchers
- **Vuetify components:** VCard, VBtn, VTextField, VIcon, VAvatar, VChip, VDataTable, etc.
- **Layout system:** Vertical nav with collapsible sidebar, sticky navbar, configurable footer
- **Theme system:** Light/dark/system modes with skin variants (default, bordered)
- **Navigation types:** `NavLink`, `NavGroup`, `NavSectionTitle` (see `resources/ts/@layouts/types.ts`)
- **Composables:** Reusable logic in `composables/` directory (e.g., `useApi.ts`)
- **State management:** Pinia stores for shared state
- **Icons:** Tabler icons via `@iconify/vue` (e.g., `tabler-smart-home`, `tabler-chart-bar`)
### Vuexy Directory Reference
```
vuexy-theme-vue-laravel-full-example-typescript/
├── resources/ts/ # TypeScript source
│ ├── @core/ # Core utilities, composables, components
│ ├── @layouts/ # Layout system (vertical/horizontal nav)
│ ├── layouts/ # Page layout templates (default, blank)
│ ├── pages/ # File-based routing pages
│ ├── plugins/ # Vue plugins (router, pinia, vuetify, i18n, casl)
│ ├── navigation/ # Nav item definitions (vertical/, horizontal/)
│ └── composables/ # App-level composables
├── resources/styles/ # SCSS with Vuetify variable overrides
├── themeConfig.ts # Global theme configuration
├── vite.config.ts # Vite with auto-import, file-based routing
└── tsconfig.json # Strict TypeScript config
```
## Code Conventions
### PHP
- PSR-12 coding standards, enforced by Pint
- `declare(strict_types=1);` in all PHP files
- Explicit return types and parameter type hints on all methods
@@ -61,29 +162,38 @@ vendor/bin/pint --dirty # Format changed files
- Check sibling files for conventions before creating new files
- Run `vendor/bin/pint --dirty --format agent` before finalizing changes
### Frontend (Vue/TypeScript)
- All components use `<script setup lang="ts">`
- Props defined via `interface Props` + `defineProps<Props>()`
- Dark mode is the default UI theme (`bg-gray-950` pages, `bg-gray-900` cards, `bg-gray-800` inputs)
- Use Inertia `Link` component for navigation (not `<a>` tags for internal links)
- Use `useForm()` from `@inertiajs/vue3` for form submissions
- Status badges use semi-transparent colored backgrounds (e.g., `bg-green-900/50 text-green-300`)
- Refer to Vuexy theme components and patterns when building new UI
## Security
- All API endpoints require authentication
- Admin routes protected by role-based middleware
- CSRF protection on all forms
- CSRF protection on all forms (webhooks exempted via `bootstrap/app.php`)
- Rate limiting on auth and API endpoints
- Encrypted storage for sensitive data (API keys, credentials)
- Audit logging for admin actions and billing events
## Domains
- **ezscale.cloud** — Marketing site
- **account.ezscale.cloud** — Customer dashboard
- **admin.ezscale.cloud** — Admin panel (Cloudflare Zero Trust)
- **status.ezscale.cloud** — Public status page
- **ezscale.dev** → **ezscale.cloud** (marketing site) — dev uses `.dev`, production will use `.cloud`
- **account.ezscale.dev** → **account.ezscale.cloud** (customer dashboard)
- **admin.ezscale.dev** → **admin.ezscale.cloud** (admin panel, Cloudflare Zero Trust)
- Subdomain routing configured in `bootstrap/app.php` via `Route::domain()`
## Key Business Domains
1. **Billing** — Subscriptions, invoices, payments (Stripe + PayPal)
1. **Billing** — Subscriptions, invoices, payments (Stripe + PayPal), dunning, coupons
2. **Provisioning** — VirtFusion (VPS), Pterodactyl (Game), SynergyCP (Dedicated), Enhance (Hosting)
3. **Customer Management** — Profiles, support tickets, notifications
4. **Admin Panel** — Dashboard, analytics, user/service management
5. **SSO** — Single sign-on via Laravel Passport
## Reference Docs
- `TASKS.md` — Task list and progress tracking
- `TASKS.md` — Task list and progress tracking (update after each phase)
- `PROJECT_DEVELOPMENT.md` — Architecture decisions, database schema, API integrations
- `FEATURES.md` — Feature specifications (35+ features)
- `ADVANCED_FEATURES.md` — Advanced feature specs (28 features)
@@ -91,3 +201,4 @@ vendor/bin/pint --dirty # Format changed files
- `GETTING_STARTED.md` — Development setup guide
- `IDEAS.md` — Future feature ideas
- `website/CLAUDE.md` — Laravel Boost guidelines (auto-generated, Laravel/Pest/Pint conventions)
- `vuexy-theme-vue-laravel-full-example-typescript/` — Vuexy theme reference (TypeScript, Vuetify, layouts)

View File

@@ -0,0 +1,670 @@
# CLAUDE.md - Vuexy Theme Reference
This is the **Vuexy Vue 3 + Laravel Admin Dashboard Template** (TypeScript edition). It serves as the UI reference for the EZSCALE billing application at `../website/`. Do NOT modify files in this directory — use it as a read-only reference for components, patterns, layouts, and styling conventions.
## Purpose
When building UI for the EZSCALE site (`../website/`), consult this theme for:
- Component patterns and TypeScript conventions
- Vuetify component usage and defaults
- Layout structure (vertical nav, navbar, footer)
- Dashboard widget composition
- Data table patterns with server-side pagination
- Form validation patterns
- Navigation item structure
- Color system and theming
- Icon usage (Tabler icons)
## Tech Stack
- **Vue 3.5** with Composition API (`<script setup lang="ts">`)
- **TypeScript 5.9** (strict mode)
- **Vuetify 3.10** — Material Design component library
- **Vite 7** with laravel-vite-plugin
- **Pinia 3** — State management
- **Vue Router 4** — File-based routing via unplugin-vue-router
- **Vue-i18n 11** — Internationalization (en, fr, ar with RTL)
- **CASL** — Access control (action + subject permissions)
- **ApexCharts + Chart.js** — Charting
- **Tabler Icons** via @iconify/vue
- **SCSS/Sass** — Styling with Vuetify variable overrides
- **VueUse** — Composition utilities
- **MSW (Mock Service Worker)** — API mocking for development
## Directory Structure
```
resources/ts/ # All TypeScript source code
├── main.ts # Entry point (creates app, registers plugins)
├── App.vue # Root component (VApp wrapper, theme CSS vars)
├── @core/ # Core theme utilities (DO NOT MODIFY)
│ ├── initCore.ts # Theme initialization (sync loader theme, skin changes, RTL)
│ ├── enums.ts # Skins (Default, Bordered), Theme (light, dark, system)
│ ├── types.ts # ExplicitThemeConfig, Options, SortItem, CustomInputContent
│ ├── stores/config.ts # ConfigStore (theme, skin, isVerticalNavSemiDark)
│ ├── composable/
│ │ ├── useSkins.ts # Skin CSS class injection, layout attributes
│ │ ├── useCookie.ts # Reactive cookie read/write
│ │ └── createUrl.ts # Query string builder for API calls
│ ├── components/
│ │ └── cards/
│ │ ├── CardStatisticsHorizontal.vue # Metric + icon (horizontal)
│ │ └── CardStatisticsVertical.vue # Metric + icon + chart (vertical)
│ ├── utils/
│ │ ├── plugins.ts # Auto-registers all plugins from plugins/ directory
│ │ ├── validators.ts # Form validators (required, email, password, url, between)
│ │ ├── helpers.ts # isEmpty, isNullOrUndefined, isEmptyArray, isObject, isToday
│ │ └── colorConverter.ts # hexToRgb, rgbaToHex
│ └── libs/ # Chart.js and ApexCharts wrapper configs
├── @layouts/ # Layout system (DO NOT MODIFY)
│ ├── config.ts # Default layout configuration
│ ├── enums.ts # ContentWidth, NavbarType, FooterType, AppContentLayoutNav
│ ├── types.ts # NavLink, NavGroup, NavSectionTitle, VerticalNavItems
│ ├── symbols.ts # Vue injection symbols
│ ├── utils.ts # Layout utility functions
│ ├── stores/config.ts # LayoutConfigStore (navbarType, isVerticalNavCollapsed, etc.)
│ └── components/
│ ├── VerticalNavLayout.vue # Main layout wrapper
│ ├── DefaultLayoutWithVerticalNav.vue # Vertical nav + navbar + content + footer
│ ├── DefaultLayoutWithHorizontalNav.vue
│ ├── VerticalNav.vue # Sidebar navigation
│ ├── VerticalNavLink.vue # Single nav link item
│ ├── VerticalNavGroup.vue # Collapsible nav group
│ ├── VerticalNavSectionTitle.vue # Section heading/divider
│ ├── HorizontalNav.vue # Top bar navigation
│ ├── HorizontalNavLink.vue
│ ├── HorizontalNavGroup.vue
│ └── HorizontalNavPopper.vue # Dropdown for horizontal nav
├── layouts/ # Page layout templates
│ ├── default.vue # Main layout (navbar + sidebar + content + footer)
│ ├── blank.vue # Blank layout (auth pages, no nav)
│ └── components/
│ ├── DefaultLayoutWithVerticalNav.vue # Composes navbar items (search, theme switcher, notifications, user profile)
│ ├── Footer.vue
│ ├── NavBarNotifications.vue
│ ├── NavSearchBar.vue
│ ├── NavbarShortcuts.vue
│ ├── NavbarThemeSwitcher.vue
│ └── UserProfile.vue
├── pages/ # File-based routing (auto-generates routes)
│ ├── login.vue # → /login (uses blank layout)
│ ├── register.vue # → /register (uses blank layout)
│ ├── dashboards/
│ │ ├── analytics.vue # → /dashboards/analytics
│ │ ├── crm.vue # → /dashboards/crm
│ │ └── ecommerce.vue # → /dashboards/ecommerce
│ ├── apps/
│ │ ├── invoice/
│ │ │ ├── list/index.vue # → /apps/invoice/list (data table + filters)
│ │ │ ├── add/index.vue # → /apps/invoice/add (form)
│ │ │ ├── edit/[id].vue # → /apps/invoice/edit/:id
│ │ │ └── preview/[id].vue # → /apps/invoice/preview/:id
│ │ ├── email/index.vue
│ │ └── chat/index.vue
│ └── front-pages/ # Marketing pages
├── views/ # Reusable view components (used by pages)
│ └── dashboards/
│ ├── analytics/ # Widget components for analytics dashboard
│ └── ecommerce/ # Widget components for ecommerce dashboard
├── plugins/ # Vue plugins (loaded alphabetically)
│ ├── 1.router/
│ │ ├── index.ts # Vue Router setup with file-based routing
│ │ ├── guards.ts # Auth guards, CASL permission checks
│ │ └── additional-routes.ts
│ ├── 2.pinia.ts # Pinia store initialization
│ ├── vuetify/
│ │ ├── index.ts # Vuetify plugin setup
│ │ ├── theme.ts # Light/dark theme color definitions
│ │ ├── defaults.ts # Component defaults (variants, colors, densities)
│ │ └── icons.ts # Icon aliases (Tabler icons)
│ ├── i18n/
│ │ ├── index.ts # Vue-i18n setup
│ │ └── locales/ # en.json, fr.json, ar.json
│ ├── casl/index.ts # CASL access control plugin
│ ├── fake-api/ # MSW mock API handlers
│ │ ├── index.ts
│ │ └── handlers/ # Mock endpoints by domain (invoice, auth, etc.)
│ ├── iconify/index.ts # Iconify icon registration
│ └── layouts.ts # Layout plugin setup
├── navigation/ # Sidebar/navbar item definitions
│ ├── vertical/
│ │ ├── index.ts # Exports all vertical nav items
│ │ ├── dashboard.ts # Dashboard nav section
│ │ └── apps-and-pages.ts # Apps & pages nav section
│ └── horizontal/
│ └── index.ts
├── composables/
│ └── useApi.ts # HTTP client (createFetch + Bearer auth + JSON parsing)
└── utils/ # App-level utilities
resources/styles/ # SCSS stylesheets
├── @core/
│ ├── base/ # Global styles, Vuetify component overrides, skins
│ └── template/ # Template-specific styles, page styles
├── variables/
│ └── _vuetify.scss # Vuetify SCSS variable overrides
└── styles.scss # Main stylesheet
themeConfig.ts # Global theme config (title, logo, layout, navbar, footer, i18n)
vite.config.ts # Vite build config with 10 plugins
tsconfig.json # Strict TypeScript with path aliases
```
## Color System
### Primary Colors
| Color | Light | Dark | Usage |
|-----------|-------------|-------------|--------------------------|
| primary | `#7367F0` | `#7367F0` | Buttons, links, accents |
| secondary | `#808390` | `#808390` | Secondary actions |
| success | `#28C76F` | `#28C76F` | Success states, paid |
| info | `#00BAD1` | `#00BAD1` | Info badges |
| warning | `#FF9F43` | `#FF9F43` | Warnings, pending |
| error | `#FF4C51` | `#FF4C51` | Errors, overdue |
### Background Colors
| Surface | Light | Dark |
|---------------|-------------|-------------|
| background | `#F8F7FA` | `#25293C` |
| surface | `#FFFFFF` | `#2F3349` |
| on-background | `#2F2B3D` | `#E1DEF5` |
| on-surface | `#2F2B3D` | `#E1DEF5` |
### Skins
- **Default** — Standard background colors
- **Bordered** — Flat backgrounds with visible borders on cards
## Vuetify Component Defaults
All components have pre-configured defaults (see `plugins/vuetify/defaults.ts`):
```
VBtn → color: 'primary'
VTextField → variant: 'outlined', density: 'comfortable', color: 'primary', hideDetails: 'auto'
VSelect → variant: 'outlined', density: 'comfortable', color: 'primary', hideDetails: 'auto'
VCheckbox → color: 'primary', density: 'comfortable', hideDetails: 'auto'
VChip → label: true (square corners)
VAvatar → variant: 'flat'
VPagination → density: 'comfortable', variant: 'tonal'
```
## Component Patterns
### Vue Component Structure
```vue
<script lang="ts" setup>
// 1. Imports
import { computed, ref, watch } from 'vue'
// 2. Type definitions
interface Props {
title: string
color?: string
items: Item[]
}
interface Item {
id: number
name: string
status: 'active' | 'inactive'
}
// 3. Props & Emits
const props = withDefaults(defineProps<Props>(), {
color: 'primary',
})
const emit = defineEmits<{
'update:modelValue': [value: string]
'submit': []
}>()
// 4. State
const isLoading = ref(false)
const searchQuery = ref('')
// 5. Computed
const filteredItems = computed(() =>
props.items.filter(item => item.name.includes(searchQuery.value))
)
// 6. Methods
const handleSubmit = async () => {
isLoading.value = true
try {
await $api('/endpoint', { method: 'POST' })
} finally {
isLoading.value = false
}
}
// 7. Watchers
watch(() => props.title, (newVal) => { /* react */ })
// 8. Lifecycle
onMounted(() => { /* init */ })
</script>
<template>
<!-- Template -->
</template>
<style lang="scss" scoped>
/* Component styles */
</style>
```
### Stat Card (Horizontal)
```vue
<CardStatisticsHorizontal
title="Total Revenue"
stats="$42,389"
icon="tabler-currency-dollar"
color="success"
/>
```
### Stat Card (Vertical with Chart)
```vue
<CardStatisticsVertical
title="Sales"
stats="$4,679"
icon="tabler-chart-bar"
color="primary"
:height="80"
:series="[{ data: [40, 20, 65, 50, 30, 60] }]"
:chart-options="chartOptions"
/>
```
### Dashboard Page Layout
```vue
<template>
<VRow class="match-height">
<!-- Full width chart -->
<VCol cols="12" md="6">
<WidgetChart />
</VCol>
<!-- Two metric cards side by side -->
<VCol cols="12" md="3" sm="6">
<CardStatisticsHorizontal title="Revenue" stats="$42k" icon="tabler-chart-bar" color="success" />
</VCol>
<VCol cols="12" md="3" sm="6">
<CardStatisticsHorizontal title="Customers" stats="1,234" icon="tabler-users" color="primary" />
</VCol>
<!-- Data table -->
<VCol cols="12">
<RecentOrdersTable />
</VCol>
</VRow>
</template>
```
### Data Table (Server-Side Pagination)
```vue
<script lang="ts" setup>
const searchQuery = ref('')
const itemsPerPage = ref(10)
const page = ref(1)
const sortBy = ref<string>()
const orderBy = ref<string>()
const headers = [
{ title: '#', key: 'id' },
{ title: 'Client', key: 'client' },
{ title: 'Status', key: 'status', sortable: false },
{ title: 'Total', key: 'total' },
{ title: 'Actions', key: 'actions', sortable: false },
]
const { data } = await useApi<any>(createUrl('/invoices', {
query: { q: searchQuery, itemsPerPage, page, sortBy, orderBy },
}))
const items = computed(() => data.value?.items ?? [])
const totalItems = computed(() => data.value?.total ?? 0)
const updateOptions = (options: any) => {
sortBy.value = options.sortBy[0]?.key
orderBy.value = options.sortBy[0]?.order
}
</script>
<template>
<VCard>
<VCardText class="d-flex justify-space-between align-center flex-wrap gap-4">
<AppTextField v-model="searchQuery" placeholder="Search..." style="inline-size: 250px;" />
</VCardText>
<VDataTableServer
v-model:items-per-page="itemsPerPage"
v-model:page="page"
:items="items"
:items-length="totalItems"
:headers="headers"
class="text-no-wrap"
@update:options="updateOptions"
>
<template #item.status="{ item }">
<VChip :color="resolveStatusColor(item.status)" size="small">
{{ item.status }}
</VChip>
</template>
<template #item.actions="{ item }">
<IconBtn size="small">
<VIcon icon="tabler-eye" />
</IconBtn>
<IconBtn size="small">
<VIcon icon="tabler-pencil" />
</IconBtn>
<IconBtn size="small" color="error">
<VIcon icon="tabler-trash" />
</IconBtn>
</template>
</VDataTableServer>
</VCard>
</template>
```
### Form with Validation
```vue
<script lang="ts" setup>
import { VForm } from 'vuetify/components/VForm'
import { emailValidator, requiredValidator } from '@validators'
const refForm = ref<VForm>()
const formData = ref({
email: '',
password: '',
name: '',
})
const onSubmit = () => {
refForm.value?.validate().then(({ valid }) => {
if (valid) {
// Submit form
}
})
}
</script>
<template>
<VForm ref="refForm" @submit.prevent="onSubmit">
<VRow>
<VCol cols="12" md="6">
<AppTextField
v-model="formData.name"
label="Name"
:rules="[requiredValidator]"
/>
</VCol>
<VCol cols="12" md="6">
<AppTextField
v-model="formData.email"
label="Email"
type="email"
:rules="[requiredValidator, emailValidator]"
/>
</VCol>
<VCol cols="12">
<VBtn type="submit" color="primary">
Submit
</VBtn>
</VCol>
</VRow>
</VForm>
</template>
```
### Status Resolver Pattern
```typescript
const resolveStatusVariant = (status: string): { color: string; icon: string } => {
const map: Record<string, { color: string; icon: string }> = {
active: { color: 'success', icon: 'tabler-check' },
pending: { color: 'warning', icon: 'tabler-clock' },
suspended: { color: 'error', icon: 'tabler-ban' },
canceled: { color: 'secondary', icon: 'tabler-x' },
}
return map[status] ?? { color: 'secondary', icon: 'tabler-circle' }
}
```
## Navigation Item Types
```typescript
// Single clickable link
interface NavLink {
title: string
to?: string | RouteLocationRaw // Route name or object
href?: string // External URL
icon?: { icon: string } // Tabler icon
badgeContent?: string // Badge text
badgeClass?: string // Badge CSS class (e.g., 'bg-error')
target?: '_blank' | '_self'
action?: string // CASL action
subject?: string // CASL subject
disable?: boolean
}
// Collapsible group with children
interface NavGroup {
title: string
icon?: { icon: string }
children: (NavLink | NavGroup)[]
badgeContent?: string
badgeClass?: string
action?: string
subject?: string
disable?: boolean
}
// Section heading/divider
interface NavSectionTitle {
heading: string
action?: string
subject?: string
}
```
### Navigation Example
```typescript
// navigation/vertical/dashboard.ts
export default [
{
title: 'Dashboards',
icon: { icon: 'tabler-smart-home' },
children: [
{ title: 'Analytics', to: 'dashboards-analytics' },
{ title: 'CRM', to: 'dashboards-crm' },
],
badgeContent: '2',
badgeClass: 'bg-error',
},
]
// navigation/vertical/apps.ts
export default [
{ heading: 'Apps & Pages' }, // Section title
{
title: 'Invoice',
icon: { icon: 'tabler-file-invoice' },
children: [
{ title: 'List', to: 'apps-invoice-list' },
{ title: 'Add', to: 'apps-invoice-add' },
],
},
{
title: 'Users',
icon: { icon: 'tabler-users' },
to: 'apps-users-list',
action: 'read', // CASL permission
subject: 'User',
},
]
```
## API Integration
### useApi Composable
```typescript
// Reactive fetch with Bearer token from cookie
const { data, execute: refetch } = await useApi<ResponseType>(
createUrl('/endpoint', {
query: { q: searchQuery, page, itemsPerPage, status: selectedStatus },
})
)
// Non-reactive one-off request
const response = await $api('/endpoint', {
method: 'POST',
body: { key: 'value' },
onResponseError({ response }) {
errors.value = response._data.errors
},
})
// Delete with refetch
const deleteItem = async (id: number) => {
await $api(`/items/${id}`, { method: 'DELETE' })
refetch()
}
```
## Validators
Available at `@validators` (auto-imported):
| Validator | Usage |
|------------------------|-------------------------------------------|
| `requiredValidator` | Field is not empty |
| `emailValidator` | Valid email format |
| `passwordValidator` | Min 8 chars, upper, lower, digit, special |
| `confirmedValidator` | Matches another field value |
| `betweenValidator` | Number within min/max range |
| `urlValidator` | Valid URL format |
| `integerValidator` | Integer only |
| `regexValidator` | Matches regex pattern |
| `alphaValidator` | Alpha characters only |
| `lengthValidator` | Exact string length |
## Layout Selection
Pages use `definePage()` macro for meta configuration:
```vue
<script lang="ts" setup>
// Use blank layout (no sidebar/navbar) — for auth pages
definePage({
meta: {
layout: 'blank',
unauthenticatedOnly: true,
},
})
</script>
```
Default layout (with sidebar + navbar) is used when no layout is specified.
## Route Guards
Authentication flow (`plugins/1.router/guards.ts`):
1. **Public routes** (`meta.public: true`) — No auth check
2. **Unauthenticated-only** (`meta.unauthenticatedOnly: true`) — Redirect to `/` if logged in
3. **CASL permission check** — If `canNavigate(to)` fails:
- Logged in → redirect to `/not-authorized`
- Not logged in → redirect to `/login?to=<path>`
## Auto-Imports (No Explicit Import Needed)
### From Vue
`ref`, `computed`, `watch`, `watchEffect`, `onMounted`, `onUnmounted`, `h`, `nextTick`, `defineProps`, `defineEmits`, `withDefaults`, `defineAsyncComponent`
### From Vue Router
`useRouter()`, `useRoute()`
### From VueUse
`useWindowSize()`, `useElementHover()`, `useMediaQuery()`, `useToggle()`, `useStorage()`, `usePreferredColorScheme()`, `useWindowScroll()`
### From Pinia
`defineStore()`, `storeToRefs()`
### From Vue-i18n
`useI18n()`
### From Theme
`useSkins()`, `useCookie()`, `createUrl()`, validators, helpers
## Icons
Uses **Tabler Icons** via Iconify. Common icons:
| Icon Name | Usage |
|--------------------------|--------------------|
| `tabler-smart-home` | Dashboard |
| `tabler-users` | Users/customers |
| `tabler-file-invoice` | Invoices |
| `tabler-shopping-cart` | Orders/commerce |
| `tabler-chart-bar` | Analytics/charts |
| `tabler-currency-dollar` | Revenue/billing |
| `tabler-server` | Servers/services |
| `tabler-shield-check` | Security |
| `tabler-settings` | Settings |
| `tabler-bell` | Notifications |
| `tabler-check` | Success/confirmed |
| `tabler-x` | Close/cancel |
| `tabler-pencil` | Edit |
| `tabler-trash` | Delete |
| `tabler-eye` | View/preview |
| `tabler-plus` | Add/create |
| `tabler-chevron-down` | Dropdown |
| `tabler-chevron-right` | Navigate |
| `tabler-menu-2` | Mobile menu toggle |
| `tabler-circle` | Default nav icon |
Usage: `<VIcon icon="tabler-smart-home" size="26" />`
## TypeScript Path Aliases
```
@/* → resources/ts/*
@themeConfig → themeConfig.ts
@layouts/* → resources/ts/@layouts/*
@core/* → resources/ts/@core/*
@images/* → resources/images/*
@styles/* → resources/styles/*
@validators → resources/ts/@core/utils/validators
```
## Key Files to Reference
| Need | Look at |
|-------------------------------|------------------------------------------------------|
| Dashboard layout | `pages/dashboards/analytics.vue` |
| Data table with filters | `pages/apps/invoice/list/index.vue` |
| Form with validation | `pages/apps/invoice/add/index.vue` |
| Auth page (login) | `pages/login.vue` |
| Stat cards | `@core/components/cards/CardStatistics*.vue` |
| Sidebar navigation | `layouts/components/DefaultLayoutWithVerticalNav.vue` |
| Nav item definitions | `navigation/vertical/dashboard.ts` |
| Theme colors | `plugins/vuetify/theme.ts` |
| Component defaults | `plugins/vuetify/defaults.ts` |
| API fetching | `composables/useApi.ts` |
| Form validators | `@core/utils/validators.ts` |
| Type definitions | `@core/types.ts`, `views/apps/invoice/types.ts` |
| Route guards | `plugins/1.router/guards.ts` |
| Theme config | `themeConfig.ts` |
| Blank layout (auth) | `layouts/blank.vue` |
| Main layout | `layouts/default.vue` |