Files
website/vuexy-theme-vue-laravel-full-example-typescript/CLAUDE.md
Claude Dev 0fe4e4ab42 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>
2026-02-09 08:00:28 -05:00

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):

  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