Files
laravel-shopping-cart/resources/js/pages/Home.vue
T
brian d9cb44e93c
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled
adding a cart
2026-04-10 08:54:20 -06:00

149 lines
5.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, ref } from 'vue';
import { Head } from '@inertiajs/vue3';
import { useHttp } from '@inertiajs/vue3';
import { store, update, destroy } from '@/actions/App/Http/Controllers/CartController';
interface Product {
id: number;
name: string;
description: string;
price_cents: number;
image_url: string | null;
}
interface CartItem {
product_id: number;
quantity: number;
}
const props = defineProps<{
products: Product[];
cart: CartItem[];
}>();
const cartItems = ref<CartItem[]>(props.cart);
const addHttp = useHttp({ product_id: 0 });
const removeHttp = useHttp({});
function addToCart(product: Product): void {
addHttp.product_id = product.id;
addHttp.post(store.url(), {
onSuccess: (data: unknown) => {
cartItems.value = data as CartItem[];
},
});
}
function removeFromCart(productId: number): void {
removeHttp.delete(destroy.url(productId), {
onSuccess: (data: unknown) => {
cartItems.value = data as CartItem[];
},
});
}
const cartWithDetails = computed(() =>
cartItems.value.map((item) => ({
...item,
product: props.products.find((p) => p.id === item.product_id)!,
})),
);
const subtotal = computed(() =>
cartWithDetails.value.reduce(
(sum, item) => sum + item.product.price_cents * item.quantity,
0,
),
);
function formatPrice(cents: number): string {
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(
cents / 100,
);
}
</script>
<template>
<Head title="Shop">
<link rel="preconnect" href="https://rsms.me/" />
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
</Head>
<div class="flex min-h-screen bg-[#FDFDFC] text-[#1b1b18] dark:bg-[#0a0a0a] dark:text-[#FDFDFC]">
<div class="flex-1 p-6 lg:p-8">
<header class="mb-8">
<h1 class="text-2xl font-semibold">Packages</h1>
</header>
<main>
<p v-if="products.length === 0" class="text-gray-500">No packages available.</p>
<div v-else class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
<div
v-for="product in products"
:key="product.id"
class="flex flex-col overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800"
>
<img
v-if="product.image_url"
:src="product.image_url"
:alt="product.name"
class="h-96 w-full object-cover"
/>
<div v-else class="flex h-48 items-center justify-center bg-gray-100 dark:bg-gray-700">
<span class="text-sm text-gray-400">No image</span>
</div>
<div class="flex flex-1 flex-col p-5">
<h2 class="mb-1 font-medium capitalize">{{ product.name }}</h2>
<p class="mb-4 grow text-sm text-gray-500 dark:text-gray-400">{{ product.description }}</p>
<div class="flex items-center justify-between">
<p class="text-lg font-semibold">{{ formatPrice(product.price_cents) }}</p>
<button
type="button"
:disabled="addHttp.processing"
class="rounded-md bg-gray-900 px-3 py-1.5 text-sm font-medium text-white hover:bg-gray-700 disabled:opacity-50 dark:bg-white dark:text-gray-900 dark:hover:bg-gray-200"
@click="addToCart(product)"
>
Add
</button>
</div>
</div>
</div>
</div>
</main>
</div>
<aside class="w-[300px] shrink-0 border-l border-gray-200 p-6 dark:border-gray-700">
<h2 class="mb-4 text-lg font-semibold">Cart</h2>
<p v-if="cartItems.length === 0" class="text-sm text-gray-500">Your cart is empty.</p>
<ul v-else class="mb-6 space-y-3">
<li
v-for="item in cartWithDetails"
:key="item.product_id"
class="flex items-center justify-between text-sm"
>
<span class="capitalize">
{{ item.product.name }}
<span class="text-gray-400">×{{ item.quantity }}</span>
</span>
<div class="flex items-center gap-2">
<span class="font-medium">{{ formatPrice(item.product.price_cents * item.quantity) }}</span>
<button
type="button"
class="text-gray-400 hover:text-red-500"
@click="removeFromCart(item.product_id)"
>
&times;
</button>
</div>
</li>
</ul>
<div class="border-t border-gray-200 pt-4 dark:border-gray-700">
<div class="flex items-center justify-between font-semibold">
<span>Subtotal</span>
<span>{{ formatPrice(subtotal) }}</span>
</div>
</div>
</aside>
</div>
</template>