Skip to content

Akash52/awesome-javascript-concepts

Repository files navigation

// src/services/api/httpClient.ts
import axios, { AxiosInstance } from 'axios'

export const createHttpClient = (): AxiosInstance => {
  const client = axios.create({
    baseURL: import.meta.env.VITE_API_URL,
  })

  client.interceptors.request.use((config) => {
    const token = localStorage.getItem('token')
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`
    }
    return config
  })

  return client
}

// src/services/api/repository.ts
import { AxiosInstance } from 'axios'

export class Repository {
  constructor(protected httpClient: AxiosInstance) {}

  protected async get<T>(url: string, params?: object): Promise<T> {
    const response = await this.httpClient.get<T>(url, { params })
    return response.data
  }

  protected async post<T>(url: string, data?: object): Promise<T> {
    const response = await this.httpClient.post<T>(url, data)
    return response.data
  }

  // Add other methods (put, delete, etc.) as needed
}

// src/services/api/productRepository.ts
import { Repository } from './repository'
import type { Product } from '@/types'

export class ProductRepository extends Repository {
  async getProducts(): Promise<Product[]> {
    return this.get<Product[]>('/products')
  }

  async getProduct(id: number): Promise<Product> {
    return this.get<Product>(`/products/${id}`)
  }
}

// src/services/api/orderRepository.ts
import { Repository } from './repository'
import type { Order } from '@/types'

export class OrderRepository extends Repository {
  async getOrders(): Promise<Order[]> {
    return this.get<Order[]>('/orders')
  }

  async createOrder(orderData: Partial<Order>): Promise<Order> {
    return this.post<Order>('/orders', orderData)
  }
}

// src/services/api/index.ts
import { createHttpClient } from './httpClient'
import { ProductRepository } from './productRepository'
import { OrderRepository } from './orderRepository'

const httpClient = createHttpClient()

export const productRepository = new ProductRepository(httpClient)
export const orderRepository = new OrderRepository(httpClient)

// src/composables/useApi.ts
import { ref, Ref } from 'vue'

export function useApi<T, E = any>(
  apiCall: () => Promise<T>
): {
  data: Ref<T | null>
  error: Ref<E | null>
  loading: Ref<boolean>
  execute: () => Promise<void>
} {
  const data = ref<T | null>(null) as Ref<T | null>
  const error = ref<E | null>(null)
  const loading = ref<boolean>(false)

  const execute = async () => {
    loading.value = true
    error.value = null
    try {
      data.value = await apiCall()
    } catch (e) {
      error.value = e as E
    } finally {
      loading.value = false
    }
  }

  return { data, error, loading, execute }
}

// src/stores/product.ts
import { defineStore } from 'pinia'
import { computed } from 'vue'
import { productRepository } from '@/services/api'
import { useApi } from '@/composables/useApi'
import type { Product } from '@/types'

export const useProductStore = defineStore('product', () => {
  const { 
    data: products, 
    error, 
    loading, 
    execute: fetchProducts 
  } = useApi<Product[]>(() => productRepository.getProducts())

  const getProductById = computed(() => (id: number) => 
    products.value?.find(p => p.id === id)
  )

  return { products, error, loading, getProductById, fetchProducts }
})

// src/stores/order.ts
import { defineStore } from 'pinia'
import { orderRepository } from '@/services/api'
import { useApi } from '@/composables/useApi'
import { useCartStore } from './cart'
import type { Order } from '@/types'

export const useOrderStore = defineStore('order', () => {
  const { 
    data: orders, 
    error, 
    loading, 
    execute: fetchOrders 
  } = useApi<Order[]>(() => orderRepository.getOrders())

  const cartStore = useCartStore()

  const createOrder = async () => {
    const { data: newOrder, error } = await useApi(() => 
      orderRepository.createOrder({
        items: cartStore.items,
        total: cartStore.total
      })
    )()

    if (newOrder.value) {
      orders.value?.push(newOrder.value)
      cartStore.clearCart()
    }

    return { newOrder, error }
  }

  return { orders, error, loading, fetchOrders, createOrder }
})

// src/composables/useFilterStrategy.ts
import { ref, computed } from 'vue'

interface Filterable {
  [key: string]: any
}

interface FilterStrategy<T extends Filterable> {
  apply(items: T[]): T[]
}

export function useFilterStrategy<T extends Filterable>() {
  const filterStrategy = ref<FilterStrategy<T> | null>(null)

  const setFilterStrategy = (strategy: FilterStrategy<T>) => {
    filterStrategy.value = strategy
  }

  const applyFilter = (items: T[]) => {
    return filterStrategy.value ? filterStrategy.value.apply(items) : items
  }

  return { filterStrategy, setFilterStrategy, applyFilter }
}

// src/composables/usePagination.ts
import { ref, computed } from 'vue'

export function usePagination<T>(items: T[], itemsPerPage: number) {
  const currentPage = ref(1)

  const paginatedItems = computed(() => {
    const startIndex = (currentPage.value - 1) * itemsPerPage
    return items.slice(startIndex, startIndex + itemsPerPage)
  })

  const totalPages = computed(() => Math.ceil(items.length / itemsPerPage))

  const nextPage = () => {
    if (currentPage.value < totalPages.value) {
      currentPage.value++
    }
  }

  const prevPage = () => {
    if (currentPage.value > 1) {
      currentPage.value--
    }
  }

  return { currentPage, paginatedItems, totalPages, nextPage, prevPage }
}

// src/composables/useNotification.ts
import { ref } from 'vue'

interface Notification {
  id: number
  message: string
  type: 'success' | 'error' | 'info'
}

export function useNotification() {
  const notifications = ref<Notification[]>([])

  const addNotification = (message: string, type: Notification['type'] = 'info') => {
    const id = Date.now()
    notifications.value.push({ id, message, type })
    setTimeout(() => removeNotification(id), 5000)
  }

  const removeNotification = (id: number) => {
    notifications.value = notifications.value.filter(n => n.id !== id)
  }

  return { notifications, addNotification, removeNotification }
}

// src/components/product/ProductList.vue
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { useProductStore } from '@/stores/product'
import { useFilterStrategy } from '@/composables/useFilterStrategy'
import { usePagination } from '@/composables/usePagination'
import ProductCard from './ProductCard.vue'
import type { Product } from '@/types'

const productStore = useProductStore()
const { filterStrategy, setFilterStrategy, applyFilter } = useFilterStrategy<Product>()

const filteredProducts = computed(() => applyFilter(productStore.products || []))
const { paginatedItems, currentPage, totalPages, nextPage, prevPage } = usePagination(filteredProducts, 10)

onMounted(() => {
  productStore.fetchProducts()
})

// Filter strategies
class PriceFilterStrategy implements FilterStrategy<Product> {
  constructor(private maxPrice: number) {}
  apply(products: Product[]): Product[] {
    return products.filter(p => p.price <= this.maxPrice)
  }
}

class CategoryFilterStrategy implements FilterStrategy<Product> {
  constructor(private category: string) {}
  apply(products: Product[]): Product[] {
    return products.filter(p => p.category === this.category)
  }
}

const applyPriceFilter = (maxPrice: number) => {
  setFilterStrategy(new PriceFilterStrategy(maxPrice))
}

const applyCategoryFilter = (category: string) => {
  setFilterStrategy(new CategoryFilterStrategy(category))
}
</script>

<template>
  <div class="product-list">
    <h1>Our Products</h1>
    <div class="filters">
      <button @click="applyPriceFilter(50)">Price under $50</button>
      <button @click="applyCategoryFilter('Electronics')">Electronics</button>
      <!-- Add more filter buttons as needed -->
    </div>
    <div v-if="productStore.loading">Loading...</div>
    <div v-else-if="productStore.error">{{ productStore.error }}</div>
    <div v-else class="products-grid">
      <ProductCard 
        v-for="product in paginatedItems" 
        :key="product.id" 
        :product="product"
      />
    </div>
    <div class="pagination">
      <button @click="prevPage" :disabled="currentPage === 1">Previous</button>
      <span>Page {{ currentPage }} of {{ totalPages }}</span>
      <button @click="nextPage" :disabled="currentPage === totalPages">Next</button>
    </div>
  </div>
</template>

// src/components/cart/ShoppingCart.vue
<script setup lang="ts">
import { useCartStore } from '@/stores/cart'
import { useOrderStore } from '@/stores/order'
import { useNotification } from '@/composables/useNotification'

const cartStore = useCartStore()
const orderStore = useOrderStore()
const { addNotification } = useNotification()

const checkout = async () => {
  const { newOrder, error } = await orderStore.createOrder()
  if (newOrder) {
    addNotification('Order placed successfully!', 'success')
  } else if (error) {
    addNotification('Failed to place order. Please try again.', 'error')
  }
}
</script>

<template>
  <div class="shopping-cart">
    <h2>Your Cart</h2>
    <ul>
      <li v-for="item in cartStore.items" :key="item.id">
        {{ item.name }} - ${{ item.price }} x {{ item.quantity }}
        <button @click="cartStore.removeItem(item.id)">Remove</button>
      </li>
    </ul>
    <p>Total: ${{ cartStore.total }}</p>
    <button @click="checkout" :disabled="cartStore.items.length === 0">
      Checkout
    </button>
  </div>
</template>

// src/plugins/errorHandler.ts
import { App } from 'vue'
import { useNotification } from '@/composables/useNotification'

export const errorHandler = {
  install(app: App) {
    const { addNotification } = useNotification()

    app.config.errorHandler = (err, vm, info) => {
      console.error(err, vm, info)
      addNotification('An error occurred. Please try again later.', 'error')
    }
  }
}

// src/main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import { errorHandler } from './plugins/errorHandler'

const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(errorHandler)
app.mount('#app')