Skip to content

Macumba Travel Frontend - Vue.js Travel Search Interface

๐ŸŽจ Frontend Overview

The Macumba Travel frontend is a modern Vue.js 3 application that provides an intuitive interface for users to search, discover, and save travel destinations. It communicates with the FastAPI backend to deliver AI-powered travel recommendations with real-time data.

๐ŸŽฏ What This Frontend Does

  1. Interactive Search Form: Collects user travel preferences (budget, dates, travelers, preferences)
  2. Real-time Recommendations: Displays AI-generated travel suggestions with rich data
  3. User Authentication: Handles login, registration, and profile management
  4. Trip Management: Allows users to save, organize, and revisit favorite trips
  5. Responsive Design: Works seamlessly across desktop, tablet, and mobile devices

๐Ÿš€ Current Technology Stack

  • Node.js: v23.11.0
  • npm: 10.9.2
  • Vue.js: 3.5.13 - Progressive JavaScript framework
  • Vite: 6.2.5 - Fast build tool and development server
  • TypeScript: Type-safe JavaScript development
  • Tailwind CSS: Utility-first CSS framework
  • Pinia: Vue state management
  • Vue Router: Client-side routing
  • Vitest: Unit testing framework
  • Playwright: End-to-end testing
  • Firebase: Authentication and hosting

๐Ÿ—๏ธ Frontend Architecture

High-Level Component Structure

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   App.vue       โ”‚  Root application component
โ”‚                 โ”‚  Navigation, routing, global state
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
         โ”‚
         โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Components     โ”‚  Reusable UI components
โ”‚                 โ”‚  SearchForm, RecommendationCard, etc.
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
         โ”‚
         โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   Services      โ”‚  API communication layer
โ”‚                 โ”‚  HTTP requests, authentication
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
         โ”‚
         โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   Store         โ”‚  State management (Pinia)
โ”‚                 โ”‚  Global application state
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Vue.js 3 Architecture Pattern

User Interaction โ†’ Component Event โ†’ Store Action โ†’ API Service โ†’ Backend
                                  โ†‘                              โ†“
UI Updates โ† Reactive Data โ† Store State โ† HTTP Response โ† Backend Response

๐Ÿ“ Detailed Directory Structure

frontend/
โ”œโ”€โ”€ public/                      # Static files served directly
โ”‚   โ”œโ”€โ”€ favicon.ico             # Website icon
โ”‚   โ””โ”€โ”€ index.html              # Main HTML template
โ”‚
โ”œโ”€โ”€ src/                        # Source code
โ”‚   โ”œโ”€โ”€ main.js                 # Application entry point
โ”‚   โ”œโ”€โ”€ App.vue                 # Root Vue component
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ components/             # Reusable Vue components
โ”‚   โ”‚   โ”œโ”€โ”€ SearchForm.vue      # Main travel search form
โ”‚   โ”‚   โ”œโ”€โ”€ RecommendationCard.vue  # Individual destination display
โ”‚   โ”‚   โ”œโ”€โ”€ LoadingSpinner.vue  # Loading state indicator
โ”‚   โ”‚   โ”œโ”€โ”€ ErrorMessage.vue    # Error display component
โ”‚   โ”‚   โ”œโ”€โ”€ Navigation.vue      # Site navigation bar
โ”‚   โ”‚   โ”œโ”€โ”€ AuthForms.vue       # Login/register forms
โ”‚   โ”‚   โ””โ”€โ”€ TripManager.vue     # Saved trips interface
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ services/               # API communication layer
โ”‚   โ”‚   โ”œโ”€โ”€ api.js              # Main HTTP client with axios
โ”‚   โ”‚   โ”œโ”€โ”€ auth.js             # Authentication services
โ”‚   โ”‚   โ””โ”€โ”€ config.js           # Environment configuration
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ store/                  # State management (Pinia)
โ”‚   โ”‚   โ”œโ”€โ”€ recommendations.js  # Travel recommendations state
โ”‚   โ”‚   โ”œโ”€โ”€ auth.js             # User authentication state
โ”‚   โ”‚   โ”œโ”€โ”€ savedTrips.js       # Saved trips management
โ”‚   โ”‚   โ””โ”€โ”€ ui.js               # UI state (loading, errors)
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ assets/                 # Static assets
โ”‚   โ”‚   โ”œโ”€โ”€ styles/             # CSS and styling files
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ main.css        # Global styles
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ tailwind.css    # Tailwind CSS imports
โ”‚   โ”‚   โ””โ”€โ”€ images/             # Image assets
โ”‚   โ”‚
โ”‚   โ””โ”€โ”€ utils/                  # Utility functions
โ”‚       โ”œโ”€โ”€ formatters.js       # Data formatting helpers
โ”‚       โ”œโ”€โ”€ validators.js       # Form validation functions
โ”‚       โ””โ”€โ”€ constants.js        # Application constants
โ”‚
โ”œโ”€โ”€ .env.development.local      # Local development (localhost:8000)
โ”œโ”€โ”€ .env.development            # Dev deployment (api-dev.macumbatravel.com)
โ”œโ”€โ”€ .env.production             # Production (api.macumbatravel.com)
โ”œโ”€โ”€ firebase.json               # Firebase hosting configuration
โ”œโ”€โ”€ package.json                # Dependencies and scripts
โ”œโ”€โ”€ vite.config.js             # Vite build configuration
โ”œโ”€โ”€ tailwind.config.js         # Tailwind CSS configuration
โ””โ”€โ”€ postcss.config.js          # PostCSS configuration

๐Ÿ”ง Key Components Explained

1. Main Search Form (SearchForm.vue)

The core component that collects user travel preferences:

<template>
  <form @submit.prevent="handleSearch" class="space-y-6">
    <!-- Budget Slider -->
    <div class="budget-section">
      <label>Total Budget: ${{ budget }}</label>
      <input type="range" v-model="budget" min="500" max="10000" step="100">
    </div>

    <!-- Date Selection -->
    <div class="date-section">
      <input type="date" v-model="fromDate" :min="today">
      <input type="date" v-model="toDate" :min="fromDate">
    </div>

    <!-- Travelers Selection -->
    <div class="travelers-section">
      <select v-model="numAdults">
        <option v-for="n in 8" :key="n" :value="n">{{ n }} Adult{{ n > 1 ? 's' : '' }}</option>
      </select>
      <select v-model="numChildren">
        <option v-for="n in 6" :key="n-1" :value="n-1">{{ n-1 }} Children</option>
      </select>
    </div>

    <!-- Preferences -->
    <div class="preferences-section">
      <label v-for="pref in availablePreferences" :key="pref">
        <input type="checkbox" :value="pref" v-model="selectedPreferences">
        {{ pref }}
      </label>
    </div>

    <button type="submit" :disabled="isLoading">
      {{ isLoading ? 'Searching...' : 'Find Destinations' }}
    </button>
  </form>
</template>

<script setup>
import { ref, computed } from 'vue'
import { useRecommendationsStore } from '@/store/recommendations'

const recommendationsStore = useRecommendationsStore()

// Form data
const budget = ref(1500)
const fromDate = ref('')
const toDate = ref('')
const numAdults = ref(2)
const numChildren = ref(0)
const selectedPreferences = ref([])

// Computed properties
const today = computed(() => new Date().toISOString().split('T')[0])
const isLoading = computed(() => recommendationsStore.isLoading)

// Form submission
const handleSearch = async () => {
  const searchParams = {
    budget: budget.value,
    fromDate: fromDate.value,
    toDate: toDate.value,
    numAdults: numAdults.value,
    numChildren: numChildren.value,
    preferences: selectedPreferences.value,
    departureCity: "Sydney, New South Wales, Australia" // Would be from autocomplete
  }

  await recommendationsStore.searchRecommendations(searchParams)
}
</script>

Key Features: - Reactive Data Binding: Form inputs automatically update component state - Computed Properties: Dynamic values like minimum dates - Store Integration: Calls Pinia store actions for API requests - Loading States: Disables form during API calls

2. API Service Layer (services/api.js)

Centralized HTTP client for all backend communication:

// services/api.js
import axios from 'axios'

// Create axios instance with base configuration
const apiClient = axios.create({
  baseURL: import.meta.env.VITE_API_URL, // Environment-specific API URL
  timeout: 30000, // 30 second timeout for travel recommendations
  headers: {
    'Content-Type': 'application/json'
  }
})

// Request interceptor - adds auth token to requests
apiClient.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('authToken')
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  (error) => Promise.reject(error)
)

// Response interceptor - handles common errors
apiClient.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      // Token expired or invalid
      localStorage.removeItem('authToken')
      window.location.href = '/login'
    }
    return Promise.reject(error)
  }
)

// API service methods
const apiService = {
  // Travel recommendations
  async getRecommendations(searchParams) {
    const response = await apiClient.post('/travel/recommendations', searchParams)
    return response.data
  },

  async enrichRecommendation(recommendationId) {
    const response = await apiClient.get(`/travel/${recommendationId}/enrich`)
    return response.data
  },

  // Authentication
  async login(credentials) {
    const response = await apiClient.post('/auth/login', credentials)
    return response.data
  },

  async register(userData) {
    const response = await apiClient.post('/auth/register', userData)
    return response.data
  },

  async getCurrentUser() {
    const response = await apiClient.get('/users/me')
    return response.data
  },

  // Saved trips
  async saveTrip(recommendationId, tripData) {
    const response = await apiClient.post(`/travel/${recommendationId}/save`, tripData)
    return response.data
  },

  async getSavedTrips() {
    const response = await apiClient.get('/users/me/trips')
    return response.data
  },

  async deleteTrip(tripId) {
    await apiClient.delete(`/trips/${tripId}`)
  },

  // Rate limiting
  async getRateLimitStatus() {
    const response = await apiClient.get('/travel/rate-limit')
    return response.data
  }
}

export default apiService

Key Features: - Environment Configuration: Automatically uses correct API URL - Authentication Handling: Adds JWT tokens to requests automatically - Error Handling: Centralized error processing and token refresh - Request/Response Interceptors: Automatic headers and error handling

3. State Management (store/recommendations.js)

Pinia store for managing travel recommendations state:

// store/recommendations.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import apiService from '@/services/api'

export const useRecommendationsStore = defineStore('recommendations', () => {
  // State
  const recommendations = ref([])
  const isLoading = ref(false)
  const error = ref(null)
  const searchParams = ref(null)
  const rateLimitInfo = ref(null)

  // Getters (computed properties)
  const hasRecommendations = computed(() => recommendations.value.length > 0)
  const sortedRecommendations = computed(() => {
    return [...recommendations.value].sort((a, b) => a.cost - b.cost)
  })
  const budgetFilteredRecommendations = computed(() => {
    if (!searchParams.value?.budget) return recommendations.value
    const maxCostPerPerson = searchParams.value.budget / getTotalTravelers()
    return recommendations.value.filter(rec => rec.cost <= maxCostPerPerson)
  })

  // Actions
  const searchRecommendations = async (params) => {
    isLoading.value = true
    error.value = null
    searchParams.value = params

    try {
      // Transform frontend format to API format
      const apiParams = {
        budget: params.budget,
        from_date: params.fromDate,
        to_date: params.toDate,
        departure_city: params.departureCity,
        preferences: params.preferences,
        max_travel_time: params.maxTravelTime || 24,
        num_adults: params.numAdults,
        num_children: params.numChildren,
        with_pets: params.withPets || false
      }

      const data = await apiService.getRecommendations(apiParams)

      // Transform API response to frontend format
      recommendations.value = data.map(rec => ({
        id: rec.id,
        destination: rec.destination,
        country: rec.country,
        cost: rec.cost, // Per person cost
        totalCost: rec.cost * getTotalTravelers(), // Calculate total cost
        travelTimeHours: rec.travel_time_hours,
        activities: rec.activities || [],
        imageUrl: rec.image_url,
        description: rec.description
      }))

    } catch (err) {
      error.value = err.response?.data?.detail || 'Failed to get recommendations'
      console.error('Search recommendations error:', err)
    } finally {
      isLoading.value = false
    }
  }

  const clearRecommendations = () => {
    recommendations.value = []
    error.value = null
    searchParams.value = null
  }

  // Helper functions
  const getTotalTravelers = () => {
    if (!searchParams.value) return 1
    return (searchParams.value.numAdults || 0) + (searchParams.value.numChildren || 0)
  }

  return {
    // State
    recommendations,
    isLoading,
    error,
    searchParams,
    rateLimitInfo,

    // Getters
    hasRecommendations,
    sortedRecommendations,
    budgetFilteredRecommendations,

    // Actions
    searchRecommendations,
    clearRecommendations
  }
})

State Management Concepts: - Reactive State: Data that automatically updates the UI when changed - Computed Properties: Derived data that recalculates when dependencies change - Actions: Functions that modify state (usually async for API calls) - Data Transformation: Converting between API format and UI format

๐Ÿ”„ Data Flow and API Integration

Complete Request-Response Cycle

  1. User Interaction

    // User fills search form and clicks "Find Destinations"
    <button @click="handleSearch">Find Destinations</button>
    

  2. Component Event Handler

    const handleSearch = async () => {
      const searchParams = {
        budget: 1500,
        fromDate: "2025-07-15",
        toDate: "2025-07-22",
        numAdults: 2,
        numChildren: 1,
        preferences: ["beach", "culture"],
        departureCity: "Sydney, New South Wales, Australia"
      }
    
      // Call store action
      await recommendationsStore.searchRecommendations(searchParams)
    }
    

  3. Store Action (Data Transformation)

    // Transform frontend format to API format
    const apiParams = {
      budget: 1500,                    // Same
      from_date: "2025-07-15",        // Snake case for API
      to_date: "2025-07-22",          // Snake case for API  
      departure_city: "Sydney, New South Wales, Australia",
      preferences: ["beach", "culture"],
      num_adults: 2,                  // Snake case for API
      num_children: 1,                // Snake case for API
      with_pets: false                // Default value
    }
    

  4. API Service Call

    // HTTP POST request to backend
    const response = await apiClient.post('/travel/recommendations', apiParams)
    

  5. Backend Processing (Invisible to frontend)

    API receives request โ†’ Validates data โ†’ AI processing โ†’ Returns results
    

  6. API Response Processing

    // Transform API response back to frontend format
    recommendations.value = data.map(rec => ({
      id: rec.id,
      destination: rec.destination,          // "Gold Coast"
      country: rec.country,                  // "Australia"
      cost: rec.cost,                        // 250 (per person)
      totalCost: rec.cost * getTotalTravelers(), // 750 (for 3 travelers)
      travelTimeHours: rec.travel_time_hours, // 1.5
      activities: rec.activities,            // ["Surfing", "Theme parks"]
      imageUrl: rec.image_url               // "https://images.pexels.com/..."
    }))
    

  7. UI Update (Automatic via Vue's reactivity)

    <!-- Recommendations automatically appear -->
    <div v-for="rec in recommendations" :key="rec.id">
      <RecommendationCard :recommendation="rec" />
    </div>
    

Environment-Based Configuration

The frontend automatically connects to different backends based on environment:

// .env.development.local (for local development)
VITE_API_URL=http://localhost:8000/api/v1

// .env.development (for staging deployment)  
VITE_API_URL=https://api-dev.macumbatravel.com/api/v1

// .env.production (for production deployment)
VITE_API_URL=https://api.macumbatravel.com/api/v1

How Environment Switching Works: 1. Vite reads the appropriate .env file based on build mode 2. API service uses import.meta.env.VITE_API_URL to get the correct URL 3. All HTTP requests automatically go to the right backend

๐ŸŽจ Styling and UI Architecture

Tailwind CSS Integration

The application uses Tailwind CSS for utility-first styling:

<!-- Example component with Tailwind classes -->
<template>
  <div class="max-w-4xl mx-auto p-6">
    <!-- Card container -->
    <div class="bg-white rounded-lg shadow-lg overflow-hidden">
      <!-- Header with gradient background -->
      <div class="bg-gradient-to-r from-blue-500 to-purple-600 text-white p-6">
        <h2 class="text-2xl font-bold">Travel Recommendations</h2>
      </div>

      <!-- Content area -->
      <div class="p-6 space-y-4">
        <!-- Loading state -->
        <div v-if="isLoading" class="flex items-center justify-center py-8">
          <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
          <span class="ml-3 text-gray-600">Finding destinations...</span>
        </div>

        <!-- Recommendations grid -->
        <div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
          <RecommendationCard 
            v-for="rec in recommendations" 
            :key="rec.id" 
            :recommendation="rec"
            class="transform hover:scale-105 transition-transform duration-200"
          />
        </div>
      </div>
    </div>
  </div>
</template>

Tailwind Benefits: - Rapid Development: Pre-built utility classes for common styles - Responsive Design: Built-in breakpoint classes (sm:, md:, lg:) - Consistent Design: Standardized spacing, colors, and typography - Small Bundle Size: Only includes used classes in production

๐Ÿ› ๏ธ Development Setup and Build Process

Local Development Environment

# 1. Install dependencies
cd frontend
npm install

# 2. Set up environment for local development
# Create .env.development.local for localhost backend
echo "VITE_API_URL=http://localhost:8000/api/v1" > .env.development.local

# 3. Start development server
npm run dev

# The frontend will be available at:
# http://localhost:5173 (or next available port)

Build Process for Different Environments

# Build for development/staging
npm run build:dev
# Uses .env.development file (api-dev.macumbatravel.com)

# Build for production
npm run build:prod  
# Uses .env.production file (api.macumbatravel.com)

# Preview production build locally
npm run preview

Firebase Deployment

# Deploy to staging
npm run deploy:dev
# 1. Builds with development environment
# 2. Deploys to Firebase hosting:development target

# Deploy to production
npm run deploy:prod
# 1. Builds with production environment  
# 2. Deploys to Firebase hosting:production target

๐Ÿ” Authentication Flow

Frontend Authentication Implementation

// store/auth.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import apiService from '@/services/api'

export const useAuthStore = defineStore('auth', () => {
  // State
  const user = ref(null)
  const token = ref(localStorage.getItem('authToken'))
  const isLoading = ref(false)

  // Getters
  const isAuthenticated = computed(() => !!token.value && !!user.value)

  // Actions
  const login = async (credentials) => {
    isLoading.value = true
    try {
      const response = await apiService.login(credentials)

      // Store token and user data
      token.value = response.access_token
      user.value = response.user

      // Persist token to localStorage
      localStorage.setItem('authToken', response.access_token)

      return { success: true }
    } catch (error) {
      return { 
        success: false, 
        error: error.response?.data?.detail || 'Login failed' 
      }
    } finally {
      isLoading.value = false
    }
  }

  const logout = () => {
    user.value = null
    token.value = null
    localStorage.removeItem('authToken')
  }

  return {
    user,
    token,
    isLoading,
    isAuthenticated,
    login,
    logout
  }
})

๐Ÿ“ฑ Responsive Design and Mobile Support

Mobile-First Design Approach

<template>
  <!-- Mobile-first grid system -->
  <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
    <!-- Cards stack on mobile, become grid on larger screens -->
  </div>

  <!-- Responsive navigation -->
  <nav class="flex flex-col md:flex-row md:items-center justify-between p-4">
    <!-- Mobile: stacked vertically, Desktop: horizontal -->
  </nav>

  <!-- Mobile-friendly form inputs -->
  <input 
    type="range" 
    class="w-full h-2 bg-gray-200 rounded-lg slider:bg-blue-600"
    :class="{ 'h-8': isMobile }" 
  >
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const isMobile = ref(false)

const checkMobile = () => {
  isMobile.value = window.innerWidth < 768
}

onMounted(() => {
  checkMobile()
  window.addEventListener('resize', checkMobile)
})

onUnmounted(() => {
  window.removeEventListener('resize', checkMobile)
})
</script>

Touch-Friendly Interactions

  • Larger touch targets: Buttons and links sized for finger taps
  • Swipe gestures: Horizontal scrolling for recommendation cards
  • Pull-to-refresh: Refresh recommendations on mobile
  • Infinite scroll: Load more results as user scrolls

๐Ÿงช Testing and Debugging

Development Debugging Tools

// Enable Vue devtools in development
if (import.meta.env.DEV) {
  window.__VUE_DEVTOOLS_GLOBAL_HOOK__ = window.__VUE_DEVTOOLS_GLOBAL_HOOK__ || {}
}

// API request logging
apiClient.interceptors.request.use((config) => {
  if (import.meta.env.DEV) {
    console.log('API Request:', config.method?.toUpperCase(), config.url, config.data)
  }
  return config
})

apiClient.interceptors.response.use(
  (response) => {
    if (import.meta.env.DEV) {
      console.log('API Response:', response.status, response.data)
    }
    return response
  },
  (error) => {
    if (import.meta.env.DEV) {
      console.error('API Error:', error.response?.status, error.response?.data)
    }
    return Promise.reject(error)
  }
)

Common Development Commands

# Development server with hot reload
npm run dev

# Build and preview production bundle
npm run build:prod
npm run preview

# Check for JavaScript/TypeScript errors
npm run lint

# Clear node_modules and reinstall (if having issues)
rm -rf node_modules package-lock.json
npm install

๐Ÿš€ Performance Optimization

Code Splitting and Lazy Loading

// Lazy load components for better performance
const SearchForm = defineAsyncComponent(() => import('@/components/SearchForm.vue'))
const RecommendationCard = defineAsyncComponent(() => import('@/components/RecommendationCard.vue'))

// Lazy load pages with routing
const routes = [
  {
    path: '/search',
    component: () => import('@/pages/SearchPage.vue')
  },
  {
    path: '/dashboard', 
    component: () => import('@/pages/Dashboard.vue')
  }
]

Image Optimization

<template>
  <!-- Responsive images with loading states -->
  <img 
    :src="optimizedImageUrl"
    :alt="destination"
    loading="lazy"
    class="w-full h-48 object-cover"
    @load="imageLoaded = true"
    @error="handleImageError"
  >
</template>

<script setup>
const optimizedImageUrl = computed(() => {
  if (!props.recommendation.image_url) return defaultImage

  // Add image optimization parameters
  const url = new URL(props.recommendation.image_url)
  url.searchParams.set('w', '400') // Width for card display
  url.searchParams.set('q', '80')  // Quality
  return url.toString()
})
</script>

๐Ÿ“š Learning Resources for Junior Developers

Key Vue.js 3 Concepts to Master

  1. Composition API: Modern way to write Vue components
  2. Reactivity System: How Vue tracks and updates data changes
  3. Component Communication: Props, events, and provide/inject
  4. State Management: Using Pinia for global state
  5. Lifecycle Hooks: Component mounting, updating, and unmounting
  1. Start with Components: Understand how Vue components work
  2. Learn State Management: Practice with Pinia stores
  3. API Integration: Study the services/api.js file
  4. Styling: Get comfortable with Tailwind CSS utilities
  5. Build Features: Try adding new components or modifying existing ones

Development Best Practices

  1. Component Structure: Keep components small and focused
  2. Naming Conventions: Use PascalCase for components, camelCase for variables
  3. Error Handling: Always handle API errors gracefully
  4. Performance: Use computed properties for derived data
  5. Accessibility: Include proper ARIA labels and keyboard navigation

This frontend documentation provides a comprehensive understanding of how the Vue.js application works, from component architecture to API integration. The key is understanding how user interactions flow through components to state management to API calls and back to the UI.