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:
159
CLAUDE.md
159
CLAUDE.md
@@ -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
|
||||
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/ # 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
|
||||
├── 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)
|
||||
|
||||
670
vuexy-theme-vue-laravel-full-example-typescript/CLAUDE.md
Normal file
670
vuexy-theme-vue-laravel-full-example-typescript/CLAUDE.md
Normal 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` |
|
||||
Reference in New Issue
Block a user