A complete e-commerce shopping cart demonstration:
Your cart is empty
shopping-cart/
├── index.ts # Shared types and injection key
├── ShoppingCart.vue # Parent cart component
└── ProductCard.vue # Individual product card
import type { InjectionKey, Ref } from 'vue';
import type { DeskCore } from '#vue-airport/composables/useCheckIn';
export interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
imageUrl?: string;
}
export interface CartContext {
products: Ref<CartItem[]>;
updateQuantity: (id: string, quantity: number) => void;
}
export const CART_DESK_KEY: InjectionKey<DeskCore<CartItem> & CartContext> = Symbol('cartDesk');
Creates the cart desk with context sharing:
<script setup lang="ts">
import { useCheckIn } from '#vue-airport/composables/useCheckIn';
import ProductCard from './ProductCard.vue';
import { type CartItem, type CartContext, CART_DESK_KEY } from '.';
// Available products catalog
const products = ref([
{
id: 'product-1',
name: 'Laptop Pro',
price: 1299.99,
quantity: 1,
imageUrl: 'i-heroicons-computer-desktop',
},
// ... more products
]);
// Create a desk for the shopping cart with context
const { createDesk } = useCheckIn<CartItem, CartContext>();
const { desk } = createDesk(CART_DESK_KEY, {
devTools: true,
debug: false,
context: {
products,
},
onCheckIn: (_id, data) => {
console.log(`Product added to cart: ${data.name}`);
},
onCheckOut: (id) => {
console.log(`Product removed from cart: ${id}`);
},
});
// Computed properties for cart data
const cartItems = computed(() => desk.getAll());
const cartCount = computed(() => {
return cartItems.value.reduce((total, item) => {
return total + item.data.quantity;
}, 0);
});
const cartTotal = computed(() => {
return cartItems.value.reduce((total, item) => {
return total + item.data.price * item.data.quantity;
}, 0);
});
// Function to remove item from cart
const removeFromCart = (id: string | number) => {
if (window.confirm('Remove this item from cart?')) {
desk.checkOut(id);
}
};
// Function to update cart item quantity
const updateCartQuantity = (id: string | number, newQty: number) => {
desk.update(id, { quantity: newQty });
};
// Function to clear the entire cart
const clearCart = () => {
if (window.confirm('Do you really want to empty the cart?')) {
desk.clear();
}
};
// Function to proceed to checkout
const checkout = () => {
if (cartItems.value.length === 0) {
// Should never happen due to button disable, but just in case
window.alert('Your cart is empty!');
return;
}
const orderSummary = cartItems.value
.map(
(item) =>
`${item.data.name} x${item.data.quantity} - $${(item.data.price * item.data.quantity).toFixed(2)}`
)
.join('\n');
window.alert(
`Order confirmed!\n\nSummary:\n${orderSummary}\n\nTotal: $${cartTotal.value.toFixed(2)}`
);
desk.clear();
};
</script>
<template>
<div>
<div class="grid grid-cols-1 lg:grid-cols-[1fr_384px] gap-8 mt-6">
<!-- Products section -->
<div>
<div class="grid grid-cols-2 gap-4">
<ProductCard v-for="product in products" :id="product.id" :key="product.id" />
</div>
</div>
<!-- Cart UI... -->
</div>
</div>
</template>
Retrieves product data from desk context:
<script setup lang="ts">
import { useCheckIn } from '#vue-airport/composables/useCheckIn';
import { type CartItem, type CartContext, CART_DESK_KEY } from '.';
import { Button } from '@/components/ui/button';
/**
* Product Card Component
* Automatically checks in and retrieves product data from desk context.
*/
const props = defineProps<{
id: string;
}>();
// Check in to the cart desk and get context
const { checkIn } = useCheckIn<CartItem, CartContext>();
const { desk } = checkIn(CART_DESK_KEY, {
id: props.id,
autoCheckIn: false,
watchData: true,
data: (desk) => {
const product = desk.products.value.find((p) => p.id === props.id);
return {
id: props.id,
name: product?.name ?? '',
price: product?.price ?? 0,
quantity: product?.quantity ?? 1,
imageUrl: product?.imageUrl,
};
},
});
// Get product data from context
const productData = computed(() => {
return desk?.products?.value.find((p) => p.id === props.id);
});
// Check if product is in the cart (checked in to desk)
const isInCart = computed(() => desk?.has(props.id) ?? false);
// Function to add product to cart
const addToCart = () => {
if (!productData.value) return;
// If already in cart, just increment quantity
if (desk?.has(props.id)) {
const currentItem = desk.get(props.id);
if (currentItem) {
const newQty = currentItem.data.quantity + 1;
desk.update(props.id, { quantity: newQty });
desk.updateQuantity(props.id, newQty);
}
} else {
// Add new item to cart by checking in
desk?.checkIn(props.id, {
id: props.id,
name: productData.value.name,
price: productData.value.price,
quantity: productData.value.quantity,
imageUrl: productData.value.imageUrl,
});
}
};
</script>
<template>
<div
class="aspect-square border border-gray-200 dark:border-gray-700 rounded-lg p-3 bg-card flex flex-col gap-2 transition-all duration-200 hover:border-primary hover:shadow-md"
>
<div class="flex justify-center items-center h-24 text-6xl text-primary opacity-70">
<UIcon :name="productData?.imageUrl || 'i-heroicons-cube'" />
</div>
<div class="flex flex-col gap-1 flex-1">
<h4 class="text-sm font-semibold m-0 truncate">{{ productData?.name }}</h4>
<div class="text-lg font-bold text-primary">${{ productData?.price.toFixed(2) }}</div>
</div>
<div class="flex flex-col gap-1.5">
<!-- Add to Cart button when not in cart -->
<Button v-if="!isInCart" size="sm" @click="addToCart">
<UIcon name="i-heroicons-shopping-cart" class="w-4 h-4 mr-2" />
Add to Cart
</Button>
</div>
</div>
</template>
The desk shares data through context, similar to the Tabs example:
const { createDesk } = useCheckIn<CartItem, CartContext>();
const { desk } = createDesk(CART_DESK_KEY, {
context: {
products,
updateQuantity,
},
});
Child components access this context via the data function:
const { checkIn } = useCheckIn<CartItem, CartContext>();
const { desk } = checkIn(CART_DESK_KEY, {
data: (desk) => {
const product = desk.products.value.find((p) => p.id === props.id);
return {
id: props.id,
name: product?.name ?? '',
price: product?.price ?? 0,
quantity: product?.quantity ?? 1,
imageUrl: product?.imageUrl,
};
},
});
Track cart operations with lifecycle hooks:
createDesk(CART_DESK_KEY, {
onCheckIn: (id, data) => {
console.log(`Product added: ${data.name}`);
},
onCheckOut: (id) => {
console.log(`Product removed: ${id}`);
},
});
Cart totals are computed from registry data:
const cartCount = computed(() => {
return cartItems.value.reduce((total, item) => {
return total + item.data.quantity;
}, 0);
});
const cartTotal = computed(() => {
return cartItems.value.reduce((total, item) => {
return total + item.data.price * item.data.quantity;
}, 0);
});
Quantities are managed through the shared context:
// Update quantity in context
const updateCartQuantity = (id: string | number, newQty: number) => {
desk.update(id, { quantity: newQty });
};
data() function with watchData: trueid is passed