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>
23 KiB
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
<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)
<CardStatisticsHorizontal
title="Total Revenue"
stats="$42,389"
icon="tabler-currency-dollar"
color="success"
/>
Stat Card (Vertical with Chart)
<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
<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)
<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
<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
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
// 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
// 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
// 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:
<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):
- Public routes (
meta.public: true) — No auth check - Unauthenticated-only (
meta.unauthenticatedOnly: true) — Redirect to/if logged in - CASL permission check — If
canNavigate(to)fails:- Logged in → redirect to
/not-authorized - Not logged in → redirect to
/login?to=<path>
- Logged in → redirect to
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 |