Appearance
Frontend Conventions
TypeScript Rules
- No
any— useunknownwith type guards - Every async function has an explicit return type
- All API response shapes are typed with interfaces in
shared/types/ - Enums from the backend are typed as union string literals
typescript
// ✅ Correct
export type VendorType = 'Manufacturer' | 'Wholesaler' | 'Agency' | 'Dealer' | 'Pharmacy'
export interface VendorDto {
id: number
name: string
vendorType: VendorType
isActive: boolean
outstandingBalance: number
createdAt: string
}
// ❌ Wrong
const vendor: any = await api.get('/vendors/1')Import Rules
Always use the @/ path alias — never relative ../../../:
typescript
// ✅ Correct
import { useAuth } from '@/shared/composables/useAuth'
import { VendorList } from '@/features/vendors'
// ❌ Wrong
import { useAuth } from '../../../shared/composables/useAuth'
import { VendorList } from '@/features/vendors/ui/VendorList.vue' // never internal filesVue SFC Conventions
<script setup lang="ts">— always use the Composition API withsetup- Default exports only for Vue SFCs — named exports for everything else
- Props typed with
defineProps<{...}>() - Emits typed with
defineEmits<{...}>()
vue
<script setup lang="ts">
interface Props {
vendorId: number
readonly?: boolean
}
const props = defineProps<Props>()
const emit = defineEmits<{
saved: [vendor: VendorDto]
cancelled: []
}>()
</script>Component Naming
- PascalCase for component files:
VendorForm.vue,AppButton.vue - Prefix shared UI with
App:AppButton,AppModal,AppDataTable - Feature components are descriptive:
VendorList,InvoiceForm,TransactionDetail
API Call Pattern
Never import axios directly. Use the API factory through the shared client:
typescript
// ✅ Correct — via factory
import { vendorApi } from '@/shared/api'
const vendors = await vendorApi.getVendors({ page: 1, pageSize: 10 })
// ❌ Wrong — never do this
import axios from 'axios'
const vendors = await axios.get('/api/vendors')State Management
- Use Pinia stores for shared state (auth, settings)
- Use local composables for feature-specific state that doesn't need to be shared
- Keep stores lean — only global persistent state goes in stores
Page Size Limits
Pages (pages/ layer) should be ~50 lines max — they compose, they don't implement:
vue
<!-- ✅ Good — 10 lines, composing features -->
<template>
<AppLayout>
<template #header><AppHeader title="Vendors" /></template>
<VendorListFeature />
</AppLayout>
</template>Environment Variables
All Vite env vars must be prefixed with VITE_:
| Variable | Default | Description |
|---|---|---|
VITE_API_BASE_URL | http://localhost:5000/api | Backend API base URL |
Access in code:
typescript
const baseUrl = import.meta.env.VITE_API_BASE_URL