Skip to content

Frontend Conventions

TypeScript Rules

  • No any — use unknown with 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 files

Vue SFC Conventions

  • <script setup lang="ts"> — always use the Composition API with setup
  • 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_:

VariableDefaultDescription
VITE_API_BASE_URLhttp://localhost:5000/apiBackend API base URL

Access in code:

typescript
const baseUrl = import.meta.env.VITE_API_BASE_URL

MedRegia — Pharmaceutical Vendor Management Platform